diff --git a/.gitignore b/.gitignore
index f2726b05eb3bb1d782333eb5bf4bd98fde02ff27..434d88e2771adfceb347838560812ce418d3a22e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -275,9 +275,14 @@ _minted*
 *.sympy
 sympy-plots-for-*.tex/
 
+# python
+*.pyc
+
 # todonotes
 *.tdo
 
 # xindy
 *.xdy
 
+# macOS
+*.DS_Store
\ No newline at end of file
diff --git a/configure.ac b/configure.ac
index edda4e608d97227633dc55c726a429ffb67d8aad..c4e9f9d971154ea3889cc3fbb696dc20e5d02fc3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -980,7 +980,7 @@ 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@:>@]
+      [external potential @<:@none, point-mass, point-mass-ring, point-mass-softened, isothermal, softened-isothermal, disc-patch, sine-wave, default: none@:>@]
    )],
    [with_potential="$withval"],
    [with_potential="none"]
@@ -1001,6 +1001,12 @@ case "$with_potential" in
    sine-wave)
       AC_DEFINE([EXTERNAL_POTENTIAL_SINE_WAVE], [1], [Sine wave external potential in 1D])
    ;; 
+   point-mass-ring)
+      AC_DEFINE([EXTERNAL_POTENTIAL_POINTMASS_RING], [1], [Point mass potential for Keplerian Ring (Hopkins 2015).])
+   ;; 
+   point-mass-softened)
+      AC_DEFINE([EXTERNAL_POTENTIAL_POINTMASS_SOFT], [1], [Softened point-mass potential with form 1/(r^2 + softening^2).])
+   ;; 
    *)
       AC_MSG_ERROR([Unknown external potential: $with_potential])
    ;;
diff --git a/examples/KeplerianRing/README.md b/examples/KeplerianRing/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..d486cbfe412c7ff9ca121c87bbc548d0ece078dd
--- /dev/null
+++ b/examples/KeplerianRing/README.md
@@ -0,0 +1,130 @@
+Keplerian Ring Test
+===================
+
+This test uses the '2D Keplerian Ring' to assess the accuracy of SPH
+calculations. For more information on the test, please see Cullen & Dehnen 2009
+(http://arxiv.org/abs/1006.1524) Section 4.3.
+
+It is key that for this test in particular that the initial conditions are
+perfectly arranged (here we use the same methodology as described in the paper
+referenced above).
+
+The test uses:
+
++ $M = 10^10$ for a central point mass
++ $r$ up to 5 (particles created to edge of box when relevant)
++ Approximately 16000 particles
++ $c_s = 0.01 \ll v_\phi = 10$, enforced by giving them a mass of 1 unit and an
+  internal energy of 0.015.
+
+Please note that the initial condition generator **requires python3 rather than
+python2**, as well as the plotting script.
+
+
+Code Setup
+----------
+
+You will need to recompile SWIFT with an external potential. This can be done
+by using
+
+    ./configure --with-ext-potential=point-mass
+
+in the root directory of the project. We suggest leaving all other code options
+as their defaults.
+
+Please also consider using:
+
+    ./configure --with-ext-potential=point-mass-softened
+
+if you are running with the initial conditions generated with the script and
+using a nonzero softening.
+
+
+Initial Conditions Generation
+-----------------------------
+
+The initial condition generation is handled by ```makeIC.py```, for which
+the options are available with
+
+    python3 makeIC.py --help
+
+however the defaults are very sensible. If you wish to change the central mass,
+you will need to change the external point mass in ```keplerian_ring.yml``` as
+well.
+
+There are three main types of generation, handled through `--generationmethod`:
+
++ `spiral`: using the original spiral method with a density distribution
+            created using different mass particles.
++ `gaussian`: a guassian density profile with density distribution changed
+              through the _distribution_ of equal mass particles.
++ `grid`: a grid with a flat density profile (similar to Hopkins 2013) imposed
+          on it through different particle masses.
+
+All of them have their benefits and drawbacks, so give each a go.
+
+Plotting
+--------
+
+Once you have ran swift (we suggest that you use the following)
+
+    ../swift -g -S -s -t 16 keplerian_ring.yml 2>&1 | tee output.log
+
+there will be around 350 ```.hdf5``` files in your directory. To check out
+the results of the example use the plotting script:
+
+    python3 plotSolution.py
+
+which will produce a png file in the directory. Check out `plotSolution.py
+--help` for more options.
+
+You can also try the `make_movie.py` script which should make a movie.
+
+
+Theory
+------
+
+We use the 'spiral' technique to generate the initial conditions. To do this,
+we first calculate
+$$
+    m_i = \frac{i - \frac{1}{2}}{N}
+$$
+for each particle $i$ with $N$ the total number of required particles. Then the
+radius of each particle is given by
+$$
+    r_i = f^{-1}(m_i)
+$$
+where for us the PDF $f$ is a Gaussian.
+
+To choose the radial positions, we then choose an initial angle $\theta_1$, and
+from there
+$$
+    \delta \theta_i = \left[2\pi\left( 1 - \frac{r_{i-1}}{r_i} \right)\right]~.
+$$
+
+### Inverse CDF of Gaussian
+
+We need:
+$$
+    f^{-1}(m_i) = r_i = r_{central} + \sqrt{2}\sigma \mathrm{erf}^{-1}(2m_i - 1),
+$$
+and thankfully scipy has an inverse error function built in.
+
+
+Implementation Details
+----------------------
+
++ The $m_i$ are generated by ```generate_m_i```.
++ The inverse Gaussian PDF is implemented as ```inverse_gaussian```.
++ The $\theta_i$ are generated by ```generate_theta_i```.
+
+
+Useful Information
+------------------
+
+$G$ in simulation units (by default using this yaml) is given by:
+$$
+    4.300970\times10^{-6}
+$$
+This is used to convert the mass of the central potential (used in the
+parameterfile) to the $GM$ required in the initial conditions generator.
diff --git a/examples/KeplerianRing/keplerian_ring.yml b/examples/KeplerianRing/keplerian_ring.yml
new file mode 100644
index 0000000000000000000000000000000000000000..421d1e89255195cd05a66975d838c59a4ad77c72
--- /dev/null
+++ b/examples/KeplerianRing/keplerian_ring.yml
@@ -0,0 +1,46 @@
+# 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:   50    # 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).
+
+# Parameters governing the snapshots
+Snapshots:
+  basename:            keplerian_ring # Common part of the name of output files
+  time_first:          0.        # Time of the first output (in internal units)
+  delta_time:          0.2      # 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:  initial_conditions.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
+PointMassPotential:
+  position_x:      5.     # location of external point mass in internal units
+  position_y:      5.     # here just take this to be the centre of the ring
+  position_z:      5.	
+  mass:            1.498351e7     # mass of external point mass in internal units
+  timestep_mult:   0.03     # controls time step
+  softening:       0.05
diff --git a/examples/KeplerianRing/makeIC.py b/examples/KeplerianRing/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..e99b7f3b5176f0c7c857e7407634caa78ed6c431
--- /dev/null
+++ b/examples/KeplerianRing/makeIC.py
@@ -0,0 +1,740 @@
+"""
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2017
+#
+# Josh Borrow (joshua.borrow@durham.ac.uk)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+"""
+
+
+import numpy as np
+import write_gadget as wg
+import h5py as h5
+
+from scipy.special import erfinv
+
+
+class Particles(object):
+    """
+    Holder class for particle properties. Also contains some methods to e.g.
+    set their keplerian velocities based on their positions. These properties
+    are set using the 'generationmethod' functions below.
+    """
+    def __init__(self, meta):
+        self.gravitymass = meta["gravitymass"]
+        self.nparts = meta["nparts"]**2
+        self.particlemass = meta["particlemass"]
+        self.softening = meta["softening"]
+        self.boxsize = meta["boxsize"]
+
+        self.smoothing = np.zeros(self.nparts) + meta["smoothing"]
+        self.internalenergy = np.zeros(self.nparts) + meta["internalenergy"]
+
+        self.positions = np.array([])
+        self.radii = np.array([])
+        self.theta = np.array([])
+        self.phi = np.array([])
+        self.velocities = np.array([])
+        self.ids = np.array([])
+        self.densities = np.array([])
+        self.masses = np.array([])
+
+        return
+
+
+    def calculate_masses(self):
+        """
+        Calculate the individual masses for the particles such that we have
+        a uniform thickness disk, given their densities.
+        """
+        mass_factor = self.particlemass / self.densities.max()
+        self.masses = self.densities * mass_factor
+
+        return self.masses
+   
+
+    def calculate_velocities(self, angle=0):
+        """
+        Calculates keplerian orbit velocities for the, well, keplerian ring.
+        Requires that radius and phi are set already.
+
+        Please give angle in radians.
+        """
+        force_modifier = np.sqrt(self.gravitymass / (self.radii**2 + self.softening**2)**(3/2)) * self.radii 
+        try:
+            v_x = np.sin(self.theta) * np.sin(self.phi) * np.cos(angle)
+        except ValueError:
+            # Phi are not yet set. Make them and then move on to v_x
+            if angle:
+                raise ValueError("Unable to find phi.")
+            
+            # If we have angle of inclination 0 we can set phi.
+            self.phi = np.zeros_like(self.theta) + np.pi / 2
+            v_x = np.sin(self.theta) * np.sin(self.phi) * np.cos(angle)
+
+        v_y = np.cos(self.phi) * np.sin(angle) - np.cos(self.theta) * np.sin(self.phi) * np.cos(angle)
+        v_z = np.sin(self.theta) * np.sin(self.phi) * np.sin(angle)
+
+        self.velocities = (force_modifier * np.array([v_x, v_y, v_z])).T
+
+        return self.velocities
+
+
+    def generate_ids(self):
+        """
+        Generate consecutive IDs from 0 based on the number of particles
+        currently in the object.
+        """
+        self.ids = np.arange(self.nparts)
+
+        return self.ids
+
+
+    def convert_polar_to_cartesian(self, centre_of_ring=(5, 5), boxsize=None):
+        """
+        Converts self.radii, self.theta to self.positions.
+
+        If self.phi is set, it also uses that to conver to z.
+        """
+        if len(self.phi) == 0:
+            x = self.radii * np.cos(self.theta) + centre_of_ring[0]
+            y = self.radii * np.sin(self.theta) + centre_of_ring[1]
+            z = np.zeros_like(x) + boxsize/2
+        else:
+            x = self.radii * np.cos(self.theta) * np.sin(self.phi) + centre_of_ring[0]
+            y = self.radii * np.sin(self.theta) * np.sin(self.phi) + centre_of_ring[1]
+            try:
+                z = self.radii * np.cos(self.phi) + centre_of_ring[2]
+            except AttributeError:
+                # Just set the central z value to the middle of the box
+                z = self.radii * np.cos(self.phi) + boxsize/2
+
+        self.positions = np.array([x, y, z]).T
+
+        return self.positions
+
+
+    def convert_cartesian_to_polar(self, centre_of_ring=(5, 5)):
+        """
+        Converts self.positions to self.radii, self.theta, and self.phi.
+        """
+        x, y, z = self.positions.T
+
+        try:
+            x = x - centre_of_ring[0]
+            y = y - centre_of_ring[1]
+            z = z - centre_of_ring[2]
+        except AttributeError:
+            raise AttributeError("Invalid array for centre_of_ring provided.")
+
+        xsquare = x*x
+        ysquare = y*y
+        zsquare = z*z
+
+        r = np.sqrt(xsquare + ysquare + zsquare)
+
+        # We need to mask over the x = 0 cases (theta = 0).
+        mask = np.isclose(x, 0, 1e-6)
+
+        masked_x = np.ma.array(x, mask=mask)
+        theta_for_unmasked = np.arctan2(y, masked_x)
+
+        theta = theta_for_unmasked + mask * 0
+
+        # Thankfully we have already ensured that r != 0.
+        phi = np.arccos(z/r)
+
+
+        self.radii = r
+        self.theta = theta
+        self.phi = phi
+
+        return r, theta, phi
+
+
+    def wiggle_positions(self, tol=1e-3):
+        """
+        'Wiggle' the positions to avoid precision issues.
+        
+        Note that this does not touch r, theta, phi.
+        """
+        self.positions += np.random.random(self.positions.shape) * tol
+
+        return
+
+
+    def exclude_particles(self, range):
+        """
+        Exclude all particles that are *not* within range (of radii).
+        """
+        mask = np.logical_or(
+            self.radii < range[0],
+            self.radii > range[1],
+        )
+
+        x  = np.ma.array(self.positions[:, 0], mask=mask).compressed()
+        y  = np.ma.array(self.positions[:, 1], mask=mask).compressed()
+        z  = np.ma.array(self.positions[:, 2], mask=mask).compressed()
+
+        self.positions = np.array([x, y, z]).T
+        
+        try:
+            v_x  = np.ma.array(self.velocities[:, 0], mask=mask).compressed()
+            v_y  = np.ma.array(self.velocities[:, 1], mask=mask).compressed()
+            v_z  = np.ma.array(self.velocities[:, 2], mask=mask).compressed()
+
+            self.velocities = np.array([v_x, v_y, v_z])
+        except IndexError:
+            # We haven't filled them yet anyway...
+            pass
+    
+        try:
+            self.ids = np.ma.array(self.ids, mask=mask).compressed()
+        except np.ma.core.MaskError:
+            # Not filled yet.
+            pass
+
+        try:
+            self.densities = np.ma.array(self.densities, mask=mask).compressed()
+        except np.ma.core.MaskError:
+            # Not filled yet.
+            pass
+
+        # QSP Fix has modified us, so first we need to chop off extras.
+        # Then, as they are all the same, we don't need to remove 'specific'
+        # values, and so we can just chop off the ends.
+        self.smoothing = self.smoothing[:len(x)]
+        self.internalenergy = self.internalenergy[:len(x)]
+
+        try:
+            self.masses = np.ma.array(self.masses, mask=mask).compressed()
+        except np.ma.core.MaskError:
+            # Not filled yet.
+            pass
+
+        self.radii = np.ma.array(self.radii, mask=mask).compressed()
+        self.theta = np.ma.array(self.theta, mask=mask).compressed()
+
+        try:
+            self.phi = np.ma.array(self.phi, mask=mask).compressed()
+        except np.ma.core.MaskError:
+            # We have not allocated our phis,
+            pass
+
+        self.nparts = len(self.radii)
+
+        return
+
+
+    def tilt_particles(self, angle, center=(5, 5, 5)):
+        """
+        Tilts the particles around the x-axis around the point given.
+
+        Assumes that the particles already have set r, theta, phi.
+        """
+        angle_radians = angle * np.pi / 180
+        rotation_matrix = np.array(
+            [
+                [  np.cos(angle_radians), 0, np.sin(angle_radians)  ],
+                [            0,           1,          0             ],
+                [ -np.sin(angle_radians), 0, np.cos(angle_radians)  ]
+            ]
+        )
+
+        self.positions = (
+            (
+                np.matmul(
+                    rotation_matrix,
+                    (self.positions - np.array(center)).T
+                )
+            ).T
+        ) + np.array(center)
+
+        self.velocities = (
+            (
+                np.matmul(
+                    rotation_matrix,
+                    (self.velocities).T
+                )
+            ).T
+        )
+        
+        self.convert_cartesian_to_polar(center)
+
+        return
+
+
+    def save_to_gadget(self, filename, boxsize=10.):
+        """
+        Save the particle data to a GADGET .hdf5 file.
+
+        Uses the internal options, but you must specify a filename.
+        """
+        with h5.File(filename, "w") as handle:
+            wg.write_header(
+                handle,
+                boxsize=boxsize,
+                flag_entropy=0,
+                np_total=np.array([self.nparts, 0, 0, 0, 0, 0]),
+                np_total_hw=np.array([0, 0, 0, 0, 0, 0]),
+                other={
+                    "MassTable" : np.array([self.particlemass, 0, 0, 0, 0, 0]),
+                    "Time" : 0,
+                }
+            )
+
+            wg.write_runtime_pars(
+                handle,
+                periodic_boundary=1,
+            )
+
+            wg.write_units(
+                handle,
+                current=1.,
+                length=1.,
+                mass=1,
+                temperature=1.,
+                time=1.,
+            )
+
+            wg.write_block(
+                handle,
+                0,  # gas
+                self.positions,
+                self.velocities,
+                self.ids,
+                mass=self.masses,
+                int_energy=self.internalenergy,
+                smoothing=self.smoothing,
+                other={"Density": self.densities},
+            )
+
+        return
+
+
+def __sigma(r):
+    """
+    Density distribution of the ring, this comes directly from Hopkins 2015.
+    """
+    if r < 0.5:
+        return 0.01 + (r/0.5)**3
+    elif r <= 2:
+        return 1.01
+    else:
+        return 0.01 + (1 + (r-2)/0.1)**(-3)
+
+
+# This is required because of the if, else statement above that does not
+# play nicely with our numpy arrays.
+sigma = np.vectorize(__sigma)
+
+
+def generate_theta(r, theta_initial=0.):
+    """
+    Generate the theta associated with the particles based on their radii.
+    This uses the method from The effect of Poisson noise on SPH calculations,
+    Annabel Cartwright, Dimitris Stamatellos and Anthony P. Whitworth 2009.
+
+    @param: r | float / array-like
+        - the radii of the particles.
+
+    @param: theta_initial | float | optional
+        - initial radius to start the generation at.
+
+    ---------------------------------------------------------------------------
+
+    @return: theta_i | numpy.array
+        - angles associated with the particles in the plane.
+    """
+    radii_fraction = r[:-1] / r[1:]
+    d_theta_i = np.sqrt(2 * np.pi * (1 - radii_fraction))
+
+    theta_i = np.empty_like(r)
+    theta_i[0] = theta_initial
+
+    # Need to do this awful loop because each entry relies on the one before it.
+    # Unless someone knows how to do this in numpy.
+    for i in range(len(d_theta_i)):  # first is theta_initial
+        theta_i[i+1] = theta_i[i] + d_theta_i[i]
+
+    return theta_i
+
+
+def QSP_fix(r_i, theta_i):
+    """
+    The start and end of the disk will have the end of the spiral there. That
+    is no good and introduces some shear forces, so we need to move them to
+    concentric circles. We'll also add some extra particles on the final
+    'layer' of this giant onion to ensure that all of the circles are complete.
+
+    @param: r_i | numpy.array
+        - the original r_i generated from inverse_gaussian (and perhaps
+          afterwards masked).
+
+    @param: theta_i | numpy.array
+        - the original theta_i generated from generate_theta_i.
+
+    ---------------------------------------------------------------------------
+
+    @return r_i_fixed | numpy.array
+        - the fixed, concentric circle-like r_i. Note that these arrays do not
+          necessarily have the same length as the r_i, theta_i that are
+          passed in and you will have to re-calculate the number of particles
+          in the system.
+
+    @return theta_i_fixed | numpy.array
+        - the fixed, concentric circle-like theta_i
+    """
+
+    # Thankfully, the delta_thetas are not wrapped (i.e. they keep on going
+    # from 2 pi -- we need to split the arrays into 'circles' by splitting
+    # the theta_i array every 2 pi.
+
+    rotations = 1
+    circles = []
+
+    these_r_i = []
+    these_theta_i = []
+
+    for radius, theta in zip(r_i, theta_i):
+        if theta > rotations * 2 * np.pi:
+            circles.append([these_r_i, these_theta_i])
+            these_r_i = []
+            these_theta_i = []
+            rotations += 1
+
+        these_r_i.append(radius)
+        these_theta_i.append(theta)
+
+    # Now we need to do the averaging.
+    # We want to have all particles in each circle a fixed radius away from the
+    # centre, as well as having even spacing between each particle. The final
+    # ring may be a bit dodgy still, but we will see.
+    
+    r_i_fixed = []
+    theta_i_fixed = []
+
+    for circle in circles:
+        n_particles = len(circle[0])
+        radius = sum(circle[0]) / n_particles
+        theta_initial = circle[1][0]
+
+        theta_sep = 2 * np.pi / n_particles
+        
+        theta = [t * theta_sep for t in range(n_particles)]
+        radii = [radius] * n_particles
+
+        r_i_fixed += radii
+        theta_i_fixed += theta
+
+    return np.array(r_i_fixed), np.array(theta_i_fixed)
+
+
+def gen_particles_grid(meta):
+    """
+    Generates particles on a grid and returns a filled Particles object.
+    """
+    particles = Particles(meta)
+    range = (0, meta["boxsize"])
+    centre_of_ring = [meta["boxsize"]/2.] * 3
+
+    # Because we are using a uniform grid we actually use the same x and y
+    # range for the initial particle setup.
+    step = (range[1] - range[0])/meta["nparts"]
+    
+    x_values = np.arange(0, range[1] - range[0], step)
+
+    # These are 2d arrays which isn't actually that helpful.
+    x, y = np.meshgrid(x_values, x_values)
+    x = x.flatten() + centre_of_ring[0] - (range[1] - range[0])/2
+    y = y.flatten() + centre_of_ring[1] - (range[1] - range[0])/2
+    z = np.zeros_like(x) + meta["boxsize"]/2
+
+    particles.positions = np.array([x, y, z]).T
+    particles.radii = np.sqrt((x - centre_of_ring[0])**2 + (y - centre_of_ring[1])**2)
+    particles.theta = np.arctan2(y - centre_of_ring[1], x - centre_of_ring[0])
+    particles.exclude_particles((particles.softening, 100.))
+
+    particles.densities = sigma(particles.radii)
+    particles.calculate_velocities()
+    particles.calculate_masses()
+
+    particles.generate_ids()
+
+    if meta["angle"]:
+        particles.tilt_particles(meta["angle"], centre_of_ring)
+
+    return particles
+
+
+def gen_particles_spiral(meta):
+    """
+    Generates particles on concentric circles and returns a filled Particles
+    object. Based on Cartwright, Stamatellos & Whitworth (2009).
+    """
+    particles = Particles(meta)
+    centre_of_ring = (meta["boxsize"]/2., meta["boxsize"]/2., meta["boxsize"]/2.)
+    max_r = meta["boxsize"]/2.
+
+    m = (np.arange(particles.nparts) + 0.5)/particles.nparts
+    r = max_r * m
+    theta = generate_theta(r)
+
+    particles.radii, particles.theta = QSP_fix(r, theta)
+    # We must do this afterwards as QSP does not always return the same number of parts.
+    phi = np.zeros_like(particles.theta) + np.pi/2
+    particles.phi = phi
+
+    particles.convert_polar_to_cartesian(centre_of_ring, meta["boxsize"])
+    particles.nparts = len(particles.radii)
+
+    particles.exclude_particles((particles.softening, 100.))
+    
+    # This way of doing densities does not work for different sized patches.
+    # Therefore we need to weight by effecitve area.
+    dtheta = (particles.theta[1:] - particles.theta[:-1]) / 2
+    areas = np.square(4 * particles.radii[1:] * np.sin(dtheta))
+    areas = np.hstack((np.array([1]), areas))
+
+    # Now do the 'actual' density calculation.
+    particles.densities = areas * sigma(particles.radii)
+    particles.calculate_velocities()
+    particles.calculate_masses()
+
+    particles.generate_ids()
+
+    if meta["angle"]:
+        particles.tilt_particles(meta["angle"], centre_of_ring)
+
+
+    return particles
+
+
+def gen_particles_gaussian(meta):
+    """
+    Generates particles on concentric circles and returns a filled Particles
+    object. Based on Cartwright, Stamatellos & Whitworth (2009).
+
+    This generation function uses a Gaussian PDF.
+    """
+    particles = Particles(meta)
+    centre_of_ring = (meta["boxsize"]/2., meta["boxsize"]/2., meta["boxsize"]/2.)
+    max_r = meta["boxsize"]/2.
+
+    m = (np.arange(particles.nparts) + 0.5)/particles.nparts
+    error_function = erfinv(2*m - 1)
+    r = 2 + 0.5 * error_function
+
+    theta = generate_theta(r)
+
+    particles.radii, particles.theta = QSP_fix(r, theta)
+    # We must do this afterwards as QSP does not always return the same number of parts.
+    phi = np.zeros_like(particles.theta) + np.pi/2
+    particles.phi = phi
+
+    particles.convert_polar_to_cartesian(centre_of_ring, meta["boxsize"])
+    particles.nparts = len(particles.radii)
+
+    particles.exclude_particles((particles.softening, 100.))
+    
+    # This way of doing densities does not work for different sized patches.
+    particles.densities = np.zeros_like(particles.radii)
+    particles.calculate_velocities()
+    particles.masses = np.ones_like(particles.radii)
+
+    particles.generate_ids()
+
+    if meta["angle"]:
+        particles.tilt_particles(meta["angle"], centre_of_ring)
+
+
+    return particles
+
+
+if __name__ == "__main__":
+    import argparse as ap
+
+    PARSER = ap.ArgumentParser(
+        description="""
+                    Initial conditions generator for the Keplerian Ring
+                    example. It has sensible defaults for GIZMO, but if you
+                    wish to run the example with SPH you sould use
+                    --generationmethod spiral.
+                    """
+    )
+
+    PARSER.add_argument(
+        "-m",
+        "--gravitymass",
+        help="""
+             GM for the central point mass. Default: 1.
+             """,
+        required=False,
+        default=1.,
+    )
+
+    PARSER.add_argument(
+        "-f",
+        "--filename",
+        help="""
+             Filename for your initial conditions.
+             Default: initial_conditions.hdf5.
+             """,
+        required=False,
+        default="initial_conditions.hdf5",
+    )
+
+    PARSER.add_argument(
+        "-n",
+        "--nparts",
+        help="""
+             Square-root of the number of particles, i.e. the default
+             nparts=128 leads to a ring with 128^2 particles in it.
+             Note that for the spiral generation method this number is 
+             somewhat approximate.
+             """,
+        required=False,
+        default=128,
+    )
+
+    PARSER.add_argument(
+        "-p",
+        "--particlemass",
+        help="""
+             Maximum mass of the gas particles. Default: 10.
+             """,
+        required=False,
+        default=10.,
+    )
+
+    PARSER.add_argument(
+        "-e",
+        "--softening",
+        help="""
+             Gravitational softening for the centre of the ring. We also
+             exclude all particles within this radius from the centre of the
+             ring. Default: 0.05.
+             """,
+        required=False,
+        default=0.05,
+    )
+
+    PARSER.add_argument(
+        "-s",
+        "--smoothing",
+        help="""
+             Initial smoothing length for all of the particles.
+             Default: Boxsize/N
+             """,
+        required=False,
+        default=-1,
+    )
+    
+    PARSER.add_argument(
+        "-i",
+        "--internalenergy",
+        help="""
+             Initial internal energy for all of the particles. Note that this
+             should be low to ensure that the ring is very cold (see Inviscid
+             SPH for details). Default: 0.015.
+             """,
+        required=False,
+        default=0.015
+    )
+
+    PARSER.add_argument(
+        "-g",
+        "--generationmethod",
+        help="""
+             Generation method for the particles. Choose between grid, spiral,
+             and gaussian, where spiral ensures that the particles are generated
+             in a way that minimises the energy in SPH. For more details on
+             this method see Cartwright, Stamatellos & Whitworth (2009).
+             Default: grid.
+             """,
+        required=False,
+        default="grid",
+    )
+
+    PARSER.add_argument(
+        "-b",
+        "--boxsize",
+        help="""
+             The box size. Default: 10.
+             """,
+        required=False,
+        default=10.
+    )
+
+    PARSER.add_argument(
+        "-a",
+        "--angle",
+        help="""
+             Angle of incline on the disk (degrees). Default: 0.
+             Note that tilting the ring may cause some issues at the
+             disk boundary as the 'box' is no longer filled with
+             particles.
+             """,
+        required=False,
+        default=0.
+    )
+
+
+    ### --- ### --- Argument Parsing --- ### --- ###
+
+    ARGS = vars(PARSER.parse_args())
+
+    if ARGS["generationmethod"] == "grid":
+        gen_particles = gen_particles_grid
+    elif ARGS["generationmethod"] == "spiral":
+        gen_particles = gen_particles_spiral
+    elif ARGS["generationmethod"] == "gaussian":
+        gen_particles = gen_particles_gaussian
+    else:
+        print(
+            "ERROR: {} is an invalid generation method. Exiting.".format(
+                ARGS["generationmethod"]
+            )
+        )
+        exit(1)
+
+
+    if ARGS["smoothing"] == -1:
+        smoothing = float(ARGS["boxsize"]) / int(ARGS["nparts"])
+    else:
+        smoothing = float(ARGS["smoothing"])
+
+
+    META = {
+        "gravitymass": float(ARGS["gravitymass"]),
+        "nparts": int(ARGS["nparts"]),
+        "particlemass": float(ARGS["particlemass"]),
+        "smoothing": smoothing,
+        "softening": float(ARGS["softening"]),
+        "internalenergy": float(ARGS["internalenergy"]),
+        "boxsize": float(ARGS["boxsize"]),
+        "angle" : float(ARGS["angle"])
+    }
+
+    PARTICLES = gen_particles(META)
+
+    PARTICLES.save_to_gadget(
+        filename=ARGS["filename"],
+        boxsize=ARGS["boxsize"],
+    )
+
diff --git a/examples/KeplerianRing/make_movie.py b/examples/KeplerianRing/make_movie.py
new file mode 100644
index 0000000000000000000000000000000000000000..83e783924086377a0b2aa384d2c4634bfd40443f
--- /dev/null
+++ b/examples/KeplerianRing/make_movie.py
@@ -0,0 +1,310 @@
+"""
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2017
+#
+# Josh Borrow (joshua.borrow@durham.ac.uk)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#
+# -----------------------------------------------------------------------------
+#
+# This program creates test plots for the initial condition generator provided
+# for the Keplerian Ring example.
+#
+###############################################################################
+"""
+
+import matplotlib
+matplotlib.use("Agg")
+
+
+import matplotlib.pyplot as plt
+import matplotlib.animation as anim
+import numpy as np
+import h5py as h5
+
+from tqdm import tqdm
+
+
+def get_colours(numbers, norm=None, cm_name="viridis"):
+    if norm is None:
+        norm = matplotlib.colors.Normalize(vmax=numbers.max(), vmin=numbers.min())
+
+    cmap = matplotlib.cm.get_cmap(cm_name)
+
+    return cmap(norm(numbers)), norm
+        
+
+def load_data(filename, silent=True, extra=None, logextra=True, exclude=None):
+    if not silent:
+        print(f"Loading data from {filename}")
+
+    with h5.File(filename, "r") as file_handle:
+        coords = file_handle['PartType0']['Coordinates'][...]
+        time = float(file_handle['Header'].attrs['Time'])
+        boxsize = file_handle['Header'].attrs['BoxSize']
+
+        # For some old runs we have z=0
+        if np.sum(coords[:, 2]) == 0:
+            centre_of_box = np.array(list(boxsize[:2]/2) + [0])
+        else:
+            centre_of_box = boxsize/2
+
+        
+        if exclude is not None:
+            distance_from_centre = coords - centre_of_box
+            r2 = np.sum(distance_from_centre * distance_from_centre, 1)
+            mask = r2 < (exclude * exclude)
+
+            masked = np.ma.array(coords, mask=np.array([mask]*3))
+
+            coords = masked.compressed()
+        
+
+        if extra is not None:
+            other = file_handle['PartType0'][extra][...]
+            if exclude is not None:
+                masked = np.ma.array(other, mask=mask)
+
+                other = masked.compressed()
+
+            if logextra:
+                other = np.log(other)
+
+            return coords, time, other
+        else:
+            return coords, time
+
+
+def rms(x):
+    return np.sqrt(sum(x**2))
+
+
+def rotation_velocity_at_r(r, params):
+    """
+    Gets the rotation velocity at a given radius r by assuming it is keplerian.
+
+    Assumes we are in cgs units, which may one day be our downfall.
+    """
+
+    unit_length = float(params[r"InternalUnitSystem:UnitLength_in_cgs"])
+
+    if unit_length != 1.:
+        print(f"Your unit length: {unit_length}")
+        raise InternalUnitSystemError(
+            "This function is only created to handle CGS units."
+        )
+
+    central_mass = float(params["PointMassPotential:mass"])
+    G = 6.674e-8
+
+    v = np.sqrt( G * central_mass / r)
+
+    return v
+
+
+def get_rotation_period_at_r(r, params):
+    """
+    Gets the rotation period at a given radius r, assuming a keplerian
+    orbit.
+    """
+    v = rotation_velocity_at_r(r, params)
+
+    return 2*np.pi / v
+
+
+def get_metadata(filename, r=1):
+    """ The metadata should be extracted from the first snapshot. """
+    with h5.File(filename, "r") as file_handle:
+        header = file_handle['Header'].attrs
+        code = file_handle['Code'].attrs
+        hydro = file_handle['HydroScheme'].attrs
+        params = file_handle['Parameters'].attrs
+
+        period = get_rotation_period_at_r(r, params)
+
+        return_values = {
+            "header" : dict(header),
+            "code" : dict(code),
+            "period" : float(period),
+            "hydro" : dict(hydro),
+            "params" : dict(params)
+        }
+
+    return return_values
+
+
+def plot_single(number, scatter, text, metadata, ax, extra=None, norm=None):
+    filename = "keplerian_ring_{:04d}.hdf5".format(number)
+
+    if extra is not None:
+        coordinates, time, other = load_data(filename, extra=extra)
+    else:
+        coordinates, time = load_data(filename)
+
+
+    text.set_text(
+        "Time: {:1.2f} | Rotations {:1.2f}".format(
+            time,
+            time/metadata['period'],
+        )
+    )
+
+    data = coordinates[:, 0:2]
+    scatter.set_offsets(data)
+
+    if extra is not None:
+        colours, _ = get_colours(other, norm)
+        scatter.set_color(colours)
+
+    return scatter,
+
+
+if __name__ == "__main__":
+    import os
+    import argparse as ap
+
+    parser = ap.ArgumentParser(
+        description="""
+                   Plots a movie of the current snapshots in your
+                   directory. Can also colourmap your information.
+                   """
+    )
+
+    parser.add_argument(
+        "-e",
+        "--exclude_central",
+        help="""
+             Region from the centre of the ring to exclude from the
+             vmin/vmax of the colourmap. Note that these particles are
+             still plotted -- they are just excluded for the purposes
+             of colourmapping. Default: 1 simulation unit.
+             """,
+        default=1.,
+        required=False
+    )
+
+    parser.add_argument(
+        "-c",
+        "--cmap",
+        help="""
+             The item from the GADGET hdf5 file to clourmap with.
+             Examples include Density, InternalEnergy.
+             Default: don't use a colourmap. (Much faster).
+             """,
+        required=False,
+        default=None
+    )
+
+    parser.add_argument(
+        "-m",
+        "--max",
+        help="""
+             Maximum radii to plot.
+             Default: 2.5.
+             """,
+        required=False,
+        default=2.5,
+    )
+    
+    args = vars(parser.parse_args())
+
+    # Look for the number of files in the directory.
+    i = 0
+    while True:
+        if os.path.isfile("keplerian_ring_{:04d}.hdf5".format(i)):
+            i += 1
+        else:
+            break
+
+        if i > 10000:
+            break
+
+
+   
+    # Now deal with the colourmapping (if present)
+    if args["cmap"] is not None:
+        _, _, numbers0 = load_data("keplerian_ring_0000.hdf5", extra=args["cmap"], exclude=float(args["exclude_central"]))
+        _, _, numbersend = load_data("keplerian_ring_{:04d}.hdf5".format(i-1), extra=args["cmap"], exclude=float(args["exclude_central"]))
+        vmax = max([numbers0.max(), numbersend.max()])
+        vmin = min([numbers0.min(), numbersend.min()])
+
+        norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
+    else:
+        norm = None
+
+
+    # Initial plot setup
+
+    metadata = get_metadata("keplerian_ring_0000.hdf5")
+    n_particle = metadata['header']['NumPart_Total'][0]
+
+    fig = plt.figure(figsize=(8, 8))
+    ax = fig.add_subplot(111)
+
+    scatter = ax.scatter([0]*n_particle, [0]*n_particle, s=0.5, marker="o")
+    diff = float(args["max"])
+    left = metadata['header']['BoxSize'][0]/2 - diff
+    right = metadata['header']['BoxSize'][0]/2 + diff
+    ax.set_xlim(left, right)
+    ax.set_ylim(left, right)
+
+    offset = 0.25
+    time_text = ax.text(
+        offset + left,
+        offset + left,
+        "Time: {:1.2f} | Rotations {:1.2f}".format(
+            0,
+            0/metadata['period'],
+        )
+    )
+
+    ax.text(
+        offset + left,
+        right-offset-0.35,
+        "Code: {} {} | {} {} \nHydro {}\n$\eta$={:1.4f}".format(
+            metadata['code']['Git Branch'].decode("utf-8"),
+            metadata['code']['Git Revision'].decode("utf-8"),
+            metadata['code']['Compiler Name'].decode("utf-8"),
+            metadata['code']['Compiler Version'].decode("utf-8"),
+            metadata['hydro']['Scheme'].decode("utf-8"),
+            metadata['hydro']['Kernel eta'][0],
+        )
+    )
+
+    ax.set_title("Keplerian Ring Test")
+    ax.set_xlabel("$x$ position")
+    ax.set_ylabel("$y$ position")
+
+    
+    anim = anim.FuncAnimation(
+        fig,
+        plot_single,
+        tqdm(np.arange(i)),
+        fargs = [
+            scatter,
+            time_text,
+            metadata,
+            ax,
+            args["cmap"],
+            norm
+        ],
+        interval=50,
+        repeat_delay=3000,
+        blit=True,
+    )
+
+    anim.save("keplerian_ring.mp4", dpi=int(640/8))
diff --git a/examples/KeplerianRing/plotSolution.py b/examples/KeplerianRing/plotSolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d6411106f7f5ddd047148927a03fbb4800e0874
--- /dev/null
+++ b/examples/KeplerianRing/plotSolution.py
@@ -0,0 +1,627 @@
+"""
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2017
+#
+# Josh Borrow (joshua.borrow@durham.ac.uk)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+###############################################################################
+"""
+
+# Plotting script for the Keplerian Ring example.
+# We use yt for the projection rendering of the ring,
+# and then our own density as a function of radius calculation.
+
+import matplotlib
+matplotlib.use("Agg")
+matplotlib.rc("text", usetex=True)
+
+try:
+    import yt
+    ytavail = True
+except ImportError:
+    print("yt not found. Falling back on homebrew plots.")
+    ytavail = False
+
+import h5py
+import os
+
+import matplotlib.pyplot as plt
+import numpy as np
+import matplotlib.gridspec as gridspec
+
+try:
+    from tqdm import tqdm
+except ImportError:
+    tqdm = lambda x: x
+
+from make_movie import get_metadata
+
+
+yt.funcs.mylog.setLevel(50)
+tqdm.monitor_interval = 0
+
+
+class InternalUnitSystemError(Exception):
+    pass
+
+
+def get_axes_grid(figure):
+    """
+    Grab our axes grid.
+    """
+    gs = gridspec.GridSpec(2, 30)
+
+    grid = []
+    
+    grid.append(figure.add_subplot(gs[0, 0:10]))
+    grid.append(figure.add_subplot(gs[0, 10:20]))
+    grid.append(figure.add_subplot(gs[0, 20:]))
+    grid.append(figure.add_subplot(gs[1, 0:9]))
+    grid.append(figure.add_subplot(gs[1, 11:20]))
+    grid.append(figure.add_subplot(gs[1, 21:]))
+
+    return grid
+
+
+def get_yt_actual_data(plot, name="density"):
+    """
+    Extracts the image data and colourmap from a yt plot.
+    
+    This is used to put on our own grid.
+    """
+
+    data = plot.plots[name].image.get_array()
+    cmap = plot.plots[name].image.cmap
+
+    return data, cmap
+
+
+def chi_square(observed, expected):
+    """
+    The chi squared statistic.
+        
+    This also looks for where expected == 0 and masks over those particles to
+    avoid divide by zero errors and unrealistically high chi squared.
+    """
+
+    mask = np.array(expected) != 0
+
+    masked_expected = np.array(expected)[mask]
+    masked_observed = np.array(observed)[mask]
+
+    return sum(((masked_observed - masked_expected)**2)/masked_expected**2)
+
+
+def load_data(filename):
+    """
+    Loads the data and extracts the relevant information for
+    calculating the chi squared statistic and density(r) profiles.
+    """
+
+    with h5py.File(filename, "r") as file:
+        boxsize = np.array(file["Header"].attrs["BoxSize"])
+
+        # Check if z = 0 for all particles. If so we need to set the cetnre
+        # to have z = 0 also.
+        if np.sum(file["PartType0"]["Coordinates"][:, 2]) == 0:
+            centre = [boxsize[0] / 2., boxsize[0] / 2., 0.]
+        else:
+            centre = boxsize / 2.
+
+        radii = np.sqrt(np.sum(((file["PartType0"]["Coordinates"][...] - centre).T)**2, 0))
+        masses = file["PartType0"]["Masses"][...]
+
+    return radii, masses
+
+
+def bin_density_r(radii, density, binrange, binnumber):
+    """
+    Bins the density as a funciton of radius.
+    """
+
+    bins = np.linspace(*binrange, binnumber)
+    indicies = np.digitize(radii, bins)
+
+    binned_masses = np.zeros(len(bins) - 1)
+    
+    for index, bin in enumerate(indicies):
+        if bin >= len(bins) - 1:
+            continue
+
+        binned_masses[bin] += density[index]
+
+    areas = [np.pi * (a**2 - b**2) for a, b in zip(bins[1:], bins[:-1])]
+    binned_densities = binned_masses/areas
+    
+    return bins, binned_densities
+
+
+def get_density_r(snapshot, filename="keplerian_ring", binrange=(0, 5), binnumber=50):
+    """
+    Gets the binned density as a function of radius.
+    """
+    snap = "{:04d}".format(snapshot)
+    filename = f"{filename}_{snap}.hdf5"
+
+    data = load_data(filename)
+
+    return bin_density_r(*data, binrange, binnumber)
+
+
+def get_mass_outside_inside(snap, radius=2, filename="keplerian_ring"):
+    """
+    Finds the mass outside and inside a radius. This is used to look at mass
+    flow inside and outside of the ring with a postprocessing routine in
+    get_derived_data.
+    """
+    snapshot = "{:04d}".format(snap)
+    filename = f"{filename}_{snapshot}.hdf5"
+
+    radii, masses = load_data(filename)
+
+    below = sum(masses[radii < radius])
+    above = sum(masses[radii > radius])
+
+    return below, above
+
+
+def get_derived_data(minsnap, maxsnap, filename="keplerian_ring", binnumber=50, radius=2):
+    """
+    Gets the derived data from our snapshots, i.e. the
+    density(r) profile and the chi squared (based on the
+    difference between the minsnap and the current snapshot).
+    """
+
+    initial = get_density_r(minsnap, filename, binnumber=binnumber)
+    other_densities = [
+        get_density_r(snap, binnumber=binnumber)[1] for snap in tqdm(
+            range(minsnap+1, maxsnap+1), desc="Densities"
+        )
+    ]
+    densities = [initial[1]] + other_densities
+
+    masses_inside_outside = [
+        get_mass_outside_inside(snap, radius=radius, filename=filename)[0] for snap in tqdm(
+            range(minsnap, maxsnap+1), desc="Mass Flow"
+        )
+    ]
+
+    # Between the initial conditions and the first snapshot we hope that there
+    # has been no mass flow, hence the [0.] + 
+    mass_flows = [0.] + [
+        y - x for x, y in zip(
+            masses_inside_outside[1:],
+            masses_inside_outside[:-1]
+        )
+    ]
+
+    cumulative_mass_flows = [
+        sum(mass_flows[:x])/masses_inside_outside[0] for x in range(len(mass_flows))
+    ]
+
+    chisq = [
+        chi_square(
+            dens[int(0.1*binnumber):int(0.4*binnumber)],
+            initial[1][int(0.1*binnumber):int(0.4*binnumber)]
+        ) for dens in tqdm(densities, desc="Chi Squared")
+    ]
+
+    return initial[0], densities, chisq, cumulative_mass_flows
+
+
+def plot_chisq(ax, minsnap, maxsnap, chisq, filename="keplerian_ring"):
+    """
+    Plot the chisq(rotation).
+    """
+    snapshots = np.arange(minsnap, maxsnap + 1)
+    rotations = [convert_snapshot_number_to_rotations_at(1, snap, filename) for snap in snapshots]
+    ax.plot(rotations, np.array(chisq)/max(chisq))
+
+    ax.set_xlabel("Number of rotations")
+    ax.set_ylabel("$\chi^2 / \chi^2_{{max}}$ = {:3.5f}".format(max(chisq)))
+
+    return
+
+
+def plot_mass_flow(ax, minsnap, maxsnap, mass_flow, filename="keplerian_ring"):
+    """
+    Plot the mass_flow(rotation).
+    """
+    snapshots = np.arange(minsnap, maxsnap + 1)
+    rotations = [convert_snapshot_number_to_rotations_at(1, snap, filename) for snap in snapshots]
+
+    ax.plot(rotations, mass_flow)
+
+    ax.set_xlabel("Number of rotations")
+    ax.set_ylabel(r"Mass flow out of ring ($M_{\rm ring}$)")
+
+    return
+
+
+def plot_density_r(ax, bins, densities, snaplist, filename="keplerian_ring"):
+    """
+    Make the density(r) plots.
+
+    Densities is the _full_ list of density profiles, and
+    snaplist is the ones that you wish to plot.
+    """
+    radii = [(x + y)/2 for x, y in zip(bins[1:], bins[:-1])]
+
+    for snap in snaplist:
+        index = snap - snaplist[0]
+        rotations = convert_snapshot_number_to_rotations_at(1, snap, filename)
+        ax.plot(radii, densities[index], label="{:2.2f} Rotations".format(rotations))
+
+    ax.legend()
+    ax.set_xlabel("Radius")
+    ax.set_ylabel("Azimuthally Averaged Surface Density")
+
+    return
+
+
+def plot_extra_info(ax, filename):
+    """
+    Plots all of the extra information on the final axis.
+
+    Give it a filename of any of the snapshots.
+    """
+
+    metadata = get_metadata(filename)
+    
+    git = metadata['code']['Git Revision'].decode("utf-8")
+    compiler_name = metadata['code']['Compiler Name'].decode("utf-8")
+    compiler_version = metadata['code']['Compiler Version'].decode("utf-8")
+    scheme = metadata['hydro']['Scheme'].decode("utf-8")
+    kernel = metadata['hydro']['Kernel function'].decode("utf-8")
+    gas_gamma = metadata["hydro"]["Adiabatic index"][0]
+    neighbors = metadata["hydro"]["Kernel target N_ngb"][0]
+    eta = metadata["hydro"]["Kernel eta"][0]
+    
+
+    ax.text(-0.49, 0.9, "Keplerian Ring with  $\\gamma={:4.4f}$ in 2/3D".format(gas_gamma), fontsize=11)
+    ax.text(-0.49, 0.8, f"Compiler: {compiler_name} {compiler_version}", fontsize=10)
+    ax.text(-0.49, 0.7, "Rotations are quoted at $r=1$", fontsize=10)
+    ax.plot([-0.49, 0.1], [0.62, 0.62], 'k-', lw=1)
+    ax.text(-0.49, 0.5, f"$\\textsc{{Swift}}$ {git}", fontsize=10)
+    ax.text(-0.49, 0.4, scheme, fontsize=10)
+    ax.text(-0.49, 0.3, kernel, fontsize=10)
+    ax.text(-0.49, 0.2, "${:2.2f}$ neighbours ($\\eta={:3.3f}$)".format(neighbors, eta), fontsize=10)
+
+    ax.set_axis_off()
+    ax.set_xlim(-0.5, 0.5)
+    ax.set_ylim(0, 1)
+
+    return
+
+
+def surface_density_plot_no_yt(ax, snapnum, filename="keplerian_ring", density_limits=None, vlim=None):
+    """
+    Make the surface density plot (sans yt).
+
+    Also returns the max and minimum values for the density so these can
+    be passed to the next call, as well as vlim which are the colourmap
+    max/min.
+    """
+
+    with h5py.File("{}_{:04d}.hdf5".format(filename, snapnum)) as filehandle:
+        density = filehandle["PartType0"]["Density"][...]
+        x, y = filehandle["PartType0"]["Coordinates"][:, 0:2].T
+
+    new_vlim = (density.min(), density.max())
+
+    if vlim is None:
+        vlim = new_vlim
+
+    ax.scatter(x, y, c=density, vmin=vlim[0], vmax=vlim[1], s=0.1)
+
+
+    metadata = get_metadata("{}_{:04d}.hdf5".format(filename, snapnum))
+    period = metadata["period"]
+
+    t = ax.text(
+        2.5,
+        7.5,
+        "Snapshot = {:04d}\nRotations = {:1.2f}".format(
+            snapnum,
+            float(metadata["header"]["Time"])/period
+        ),
+        color='black'
+    )
+
+    t.set_bbox(dict(alpha=0.5, color="white"))
+
+    ax.axis("equal")
+
+    ax.set_xlim(2, 8)
+    ax.set_ylim(2, 8)
+
+    # We now want to remove all of the ticklabels.
+
+    for axis in ['x', 'y']:
+        ax.tick_params(
+            axis=axis,          
+            which='both',      
+            bottom='off',      
+            top='off',         
+            left='off',
+            right='off',
+            labelleft='off',
+            labelbottom='off'
+        ) 
+
+    return density_limits, vlim
+
+
+
+def surface_density_plot(ax, snapnum, filename="keplerian_ring", density_limits=None, vlim=None):
+    """
+    Make the surface density plot (via yt).
+
+    Also returns the max and minimum values for the density so these can
+    be passed to the next call, as well as vlim which are the colourmap
+    max/min.
+    """
+
+    unit_base = {
+        'length': (1.0, 'cm'),
+        'velocity': (1.0, 'cm/s'),
+        'mass': (1.0, 'g')
+    }
+
+    filename = "{}_{:04d}.hdf5".format(filename, snapnum)
+
+    try:
+        snap = yt.load(filename, unit_base=unit_base, over_refine_factor=2)
+    except yt.utilities.exceptions.YTOutputNotIdentified:
+        # Probably the file isn't here because we supplied a too high snapshot
+        # number. Just return what we're given.
+        return density_limits, vlim
+
+    projection_plot = yt.ProjectionPlot(
+        snap,
+        "z",
+        ("gas", "cell_mass"),
+        width=5.5
+    )
+
+    max_density = snap.all_data()[("gas", "cell_mass")].max()
+    min_density = snap.all_data()[("gas", "cell_mass")].min()
+    
+    new_density_limits = (min_density, max_density)
+
+    if density_limits is None:
+        density_limits = new_density_limits
+
+    projection_plot.set_zlim("cell_mass", *density_limits)
+
+    data = get_yt_actual_data(projection_plot, ("gas", "cell_mass"))
+
+    # Becuase of the way plotting works, we also need a max/min for the colourmap.
+
+    new_vlim = (data[0].min(), data[0].max())
+
+    if vlim is None:
+        vlim = new_vlim
+
+    ax.imshow(
+        data[0],
+        cmap=data[1],
+        vmin=vlim[0],
+        vmax=vlim[1]
+    )
+
+    metadata = get_metadata(filename)
+    period = metadata["period"]
+
+    ax.text(
+        20,
+        80,
+        "Snapshot = {:04d}\nRotations = {:1.2f}".format(
+            snapnum,
+            float(snap.current_time)/period
+        ),
+        color='white'
+    )
+
+    # We now want to remove all of the ticklabels.
+
+    for axis in ['x', 'y']:
+        ax.tick_params(
+            axis=axis,          
+            which='both',      
+            bottom='off',      
+            top='off',         
+            left='off',
+            right='off',
+            labelleft='off',
+            labelbottom='off'
+        ) 
+
+    return density_limits, vlim
+
+
+def convert_snapshot_number_to_rotations_at(r, snapnum, filename):
+    """
+    Opens the file and extracts metadata to find the number of rotations.
+    """
+
+    metadata = get_metadata("{}_{:04d}.hdf5".format(filename, snapnum))
+
+    t = metadata["period"]
+    current_time = float(metadata["header"]["Time"])
+
+    return current_time / t
+
+
+if __name__ == "__main__":
+    import argparse as ap
+
+    parser = ap.ArgumentParser(
+        description="""
+                    Plotting code for the Keplerian Ring test. Uses yt to make
+                    surface density plots.
+                    """
+    )
+
+    parser.add_argument(
+        "-b",
+        "--beginning",
+        help="""
+             Initial snapshot to start analysis at.
+             Default: First snapshot in the folder.
+             """,
+        default=-1,
+        required=False
+    )
+
+    parser.add_argument(
+        "-m",
+        "--middle",
+        help="""
+             Middle snapshot in the top row of three surface density plots.
+             Default: (end - beginning) // 2.
+             """,
+        default=-1,
+        required=False
+    )
+
+    parser.add_argument(
+        "-e",
+        "--end",
+        help="""
+             Snapshot to end the analysis at.
+             Default: Last snapshot in the folder.
+             """,
+        default=-1,
+        required=False
+    )
+
+    parser.add_argument(
+        "-f",
+        "--filename",
+        help="""
+             Filename of the output.
+             Default: plot.png
+             """,
+        default="plot.png",
+        required=False
+    )
+
+    parser.add_argument(
+        "-n",
+        "--nbins",
+        help="""
+             Number of bins in histogramming (used for the density(r) plots).
+             Default: 100.
+             """,
+        default=100,
+        required=False
+    )
+
+    parser.add_argument(
+        "-p",
+        "--plotmassflow",
+        help="""
+             Plot the mass flow instead of several density(r) plots.
+             Set this to a nonzero number to do this plot.
+             Default: 0.
+             """,
+        default=0,
+        required=False
+    )
+
+    parser.add_argument(
+        "-s",
+        "--slug",
+        help="""
+             The first part of the output filename. For example, for snapshots
+             with the naming scheme keplerian_ring_xxxx.hdf5, this would be the
+             default value of keplerian_ring.
+             """,
+        default="keplerian_ring",
+        required=False
+    )
+    
+    parser.add_argument(
+        "-y",
+        "--yt",
+        help="""
+             Use yt to do the plots at the top of the page. If set to anything
+             other than a 'truthy' value, we will use a homebrew plotting
+             setup. Default: False
+             """,
+        default=False,
+        required=False
+    )
+
+    args = vars(parser.parse_args())
+
+    filename = args["slug"]
+
+    if args["beginning"] == -1:
+        # We look for the maximum number of snapshots.
+        numbers = [
+            int(x[len(filename)+1:-5])
+            for x in os.listdir() if
+            x[:len(filename)] == filename and x[-1] == "5"
+        ]
+
+        snapshots = [min(numbers), (max(numbers) - min(numbers)) // 2, max(numbers)]
+    else:
+        snapshots = [args["beginning"], args["middle"], args["end"]]
+
+    figure = plt.figure(figsize=(12, 10))
+    axes = get_axes_grid(figure)
+
+    density_limits = None
+    vlim = None
+
+    for snap, ax in zip(snapshots, tqdm(axes[0:3], desc="Images")):
+        if args["yt"] and ytavail:
+            density_limits, vlim = surface_density_plot(
+                ax,
+                snap,
+                density_limits=density_limits,
+                vlim=vlim
+            )
+
+            figure.subplots_adjust(hspace=0, wspace=0)
+        else:
+            density_limits, vlim = surface_density_plot_no_yt(
+                ax,
+                snap,
+                density_limits=density_limits,
+                vlim=vlim
+            )
+
+    # Now we need to do the density(r) plot.
+
+    # Derived data includes density profiles and chi squared
+    derived_data = get_derived_data(snapshots[0], snapshots[2], binnumber=int(args["nbins"]))
+
+    if args["plotmassflow"]:
+        plot_mass_flow(axes[3], snapshots[0], snapshots[2], derived_data[3])
+    else:
+        plot_density_r(axes[3], derived_data[0], derived_data[1], snapshots)
+
+    plot_chisq(axes[4], snapshots[0], snapshots[2], derived_data[2])
+
+    plot_extra_info(axes[5], "keplerian_ring_0000.hdf5")
+
+    figure.savefig(args["filename"], dpi=300)
+
+
diff --git a/examples/KeplerianRing/run.sh b/examples/KeplerianRing/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ee8f7b247f38b74a204f9caf2bd543b72c4f94fa
--- /dev/null
+++ b/examples/KeplerianRing/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# Generate the initial conditions if they are not present.
+if [ ! -e initial_conditions.hdf5 ]
+then
+    echo "Generating initial conditions for the keplerian ring example..."
+    echo "Please consider choosing your own options before continuing..."
+    python3 makeIC.py
+fi
+
+rm -rf keplerian_ring_*.hdf5
+../swift -g -s -t 1 -v 1 keplerian_ring.yml 2>&1 | tee output.log
diff --git a/examples/KeplerianRing/testplots.py b/examples/KeplerianRing/testplots.py
new file mode 100644
index 0000000000000000000000000000000000000000..1de4964a5618636b73657eec229cbf6e83ef0ff9
--- /dev/null
+++ b/examples/KeplerianRing/testplots.py
@@ -0,0 +1,45 @@
+"""
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2017
+#
+# Josh Borrow (joshua.borrow@durham.ac.uk)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#
+# -----------------------------------------------------------------------------
+#
+# This program creates test plots for the initial condition generator provided
+# for the Keplerian Ring example.
+#
+###############################################################################
+"""
+
+
+if __name__ == "__main__":
+    import yt
+
+    data = yt.load("initial_conditions.hdf5")
+
+    plot = yt.ParticlePlot(
+        data,
+        "particle_position_x",
+        "particle_position_y",
+        "density"
+    )
+
+    plot.save("test.png")
+
+
diff --git a/examples/KeplerianRing/write_gadget.py b/examples/KeplerianRing/write_gadget.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2519cdaf4d78d9c7327419283072b8001e70fcb
--- /dev/null
+++ b/examples/KeplerianRing/write_gadget.py
@@ -0,0 +1,309 @@
+"""
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2017
+#
+# Josh Borrow (joshua.borrow@durham.ac.uk)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# -----------------------------------------------------------------------------
+#
+# This program is a set of helper functions that write particle data to a
+# GADGET .hdf5 file *handle*. It should also function as a piece of
+# documentation on the required attributes for SWIFT to function when running
+# in GADGET compatibility mode.
+# 
+# Example Usage:
+#
+# import write_gadget as wg
+# import h5py as h5
+$ import numpy as np
+#
+#
+# N_PARTICLES = 1000
+#
+#
+# with h5.File("test.hdf5", "w") as f:
+#    wg.write_header(
+#        f,
+#        boxsize=100.,
+#        flag_entropy=1,
+#        np_total=[N_PARTICLES, 0, 0, 0, 0, 0],
+#        np_total_hw=[0, 0, 0, 0, 0, 0]
+#    )
+#
+#    wg.write_runtime_pars(
+#        f,
+#        periodic_boundary=1,
+#    )
+#
+#    wg.write_units(
+#        f,
+#        current=1.,
+#        length=1.,
+#        mass=1.,
+#        temperature=1.,
+#        time=1.
+#    )
+#
+#    wg.write_block(
+#       f,
+#       part_type=0,
+#       pos=np.array([np.arange(N_PARTICLES)] * 3).T,
+#       vel=np.array([np.zeros(N_PARTICLES)] * 3).T,
+#       ids=np.arange(N_PARTICLES),
+#       mass=np.ones(N_PARTICLES,
+#       int_energy=np.zeros(N_PARTICLES),
+#       smoothing=np.ones(NP_ARTICLES),
+#
+###############################################################################
+"""
+
+
+def write_header(f, boxsize, flag_entropy, np_total, np_total_hw, other=False):
+    """ Writes the "Header" section of the hdf5 file. The parameters in this
+        function that are required are the ones that are required for SWIFT
+        to function; note that the MassTable is **ignored** and that all
+        particle masses should be placed into the particle data arrays.
+
+        @param: f | file handle
+            - the file handle of the hdf5 object (use h5py.File(filename, "w")
+              to open a file handle of the correct type).
+
+        @param boxsize | float / list (2D / 3D)
+            - the boxsize. If a float is given it is assumed that the box is
+              the same size in all directions.
+
+        @param flag_entropy | int (0/1)
+            - sets Flag_Entropy_ICs. This is a historical variable for cross
+              compatibility with Gadget-3
+
+        @param np_total | list (6D)
+            - the total number of particles required of each type.
+
+                Type/Index | Symbolic Type Name
+               ------------|--------------------
+                    0      |       Gas
+                    1      |       Halo
+                    2      |       Disk
+                    3      |       Bulge
+                    4      |       Stars
+                    5      |       Bndry
+
+        @param np_total_hw | list (6D)
+            - the number of high-word particles in the file.
+
+
+        @param other | dictionary | optional
+            - a dictionary with any other parameters that you wish to pass into
+              the file header. They will be passed such that the key is the
+              name of the attribute in the hdf5 file.
+
+    """
+
+    # We'll first build a dictionary to iterate through.
+
+    default_attributes = {
+        "BoxSize" : boxsize,
+        "Flag_Entropy_ICs" : flag_entropy,
+        "NumPart_Total" : np_total,
+        "NumPart_Total_HighWord" : np_total_hw,
+        "NumFilesPerSnapshot" : 1,  # required for backwards compatibility
+        "NumPart_ThisFile" : np_total, # Also required for bw compatibility
+    }
+
+    if other:
+        attributes = dict(default_attributes, **other)
+    else:
+        attributes = default_attributes
+
+    header = f.create_group("Header")
+
+    # For some reason there isn't a direct dictionary interface (at least one
+    # that is documented, so we are stuck doing this loop...
+
+    for name, value in attributes.items():
+        header.attrs[name] = value
+
+    return
+
+
+def write_runtime_pars(f, periodic_boundary, other=False):
+    """ Writes the "RuntimeParams" section in the hdf5 file. The parameters in
+        this function that are required are also required for SWIFT to function.
+        If you wish to pass extra arguments into the runtime parameters you
+        may do that by providing a dictionary to other.
+
+        @param: f | file handle
+            - the file handle of the hdf5 object (use h5py.File(filename, "w")
+              to open a file handle of the correct type).
+
+        @param: periodic_boundary | int (0/1)
+            - the condition for periodic boundary conditions -- they are 'on'
+              if this variable is 1, and off if it is 0. Note that SWIFT
+              currently requires periodic boundary conditions to run (as of
+              August 2017).
+
+        @param other | dictionary | optional
+            - a dictionary with any other parameters that you wish to pass into
+              the RuntimePars. They will be passed such that the key is the
+              name of the attribute in the hdf5 file.
+    """
+
+    # First build the dictionary
+
+    default_attributes = {
+        "PeriodicBoundariesOn" : periodic_boundary,
+    }
+
+    if other:
+        attributes = dict(default_attributes, **other)
+    else:
+        attributes = default_attributes
+
+    runtime = f.create_group("RuntimePars")
+
+    for name, value in attributes.items():
+        runtime.attrs[name] = value
+
+    return
+
+
+def write_units(f, current, length, mass, temperature, time, other=False):
+    """ Writes the "RuntimeParams" section in the hdf5 file. The parameters in
+        this function that are required are also required for SWIFT to function.
+        If you wish to pass extra arguments into the runtime parameters you
+        may do that by providing a dictionary to other.
+
+        @param: f | file handle
+            - the file handle of the hdf5 object (use h5py.File(filename, "w")
+              to open a file handle of the correct type).
+
+        @param: current | float
+            - the current conversion factor in cgs units.
+
+        @param: length | float
+            - the length conversion factor in cgs units.
+
+        @param: mass | float
+            - the mass conversion factor in cgs units.
+
+        @param: temperature | float
+            - the temperature conversion factor in cgs units.
+
+        @param: time | float
+            - the time conversion factor in cgs units.
+
+        @param: other | dictionary | optional
+            - a dictionary with any other parameters that you wish to pass into
+              the units attributes. They will be passed such that the key is
+              the name of the attribute in the hdf5 file.
+    """
+
+    # First build the dictionary
+
+    default_attributes = {
+        "Unit current in cgs (U_I)": current,
+        "Unit length in cgs (U_L)": length,
+        "Unit mass in cgs (U_M)": mass,
+        "Unit temperature in cgs (U_T)": temperature,
+        "Unit time in cgs (U_t)": time,
+    }
+
+    if other:
+        attributes = dict(default_attributes, **other)
+    else:
+        attributes = default_attributes
+
+    units = f.create_group("Units")
+
+    for name, value in attributes.items():
+        units.attrs[name] = value
+
+    return
+
+
+def write_block(f, part_type, pos, vel, ids, mass, int_energy, smoothing, other=False):
+    """ Writes a given block of data to PartType{part_type}. The above
+        required parameters are required for SWIFT to run.
+
+        @param: f | file handle
+            - the file handle of the hdf5 object.
+
+        @param part_type | int (0-5):
+            - the identifiying number of the particle type.
+
+                Type/Index | Symbolic Type Name
+               ------------|--------------------
+                    0      |       Gas
+                    1      |       Halo
+                    2      |       Disk
+                    3      |       Bulge
+                    4      |       Stars
+                    5      |       Bndry
+
+        @param pos | numpy.array
+            - the array of particle positions with shape (n_particles, 3).
+
+        @param vel | numpy.array
+            - the array of particle velocities with shape (n_particles, 3).
+
+        @param ids | numpy.array
+            - the ids of the particles. Please note that particle IDs in
+              SWIFT must be strictly positive. Shape (n_particles, 1)
+
+        @param mass | numpy.array
+            - the masses of the particles. In SWIFT MassTable in the header
+              is ignored and so particle masses must be provided here. Shape
+              (n_particles, 1)
+
+        @param int_energy | numpy.array
+            - the internal energies of the particles. Shape (n_particles, 1)
+
+        @param smoothing | numpy.array
+            - the smoothing lenghts of the individual particles. Please note 
+              that this cannot be supplied only in the parameterfile and must
+              be provided on a particle-by-particle basis in SWIFT. Shape
+              (n_particles, 1)
+
+        @param: other | dictionary | optional
+            - a dictionary with any other parameters that you wish to pass into
+              the particle data. They will be passed such that the key is
+              the name of the dataset in the hdf5 file.
+    """
+    
+    # Build the dictionary
+
+    default_data = {
+        "Coordinates" : pos,
+        "Velocities" : vel,
+        "ParticleIDs" : ids,
+        "Masses" : mass,
+        "InternalEnergy" : int_energy,
+        "SmoothingLength" : smoothing,
+    }
+
+    if other:
+        data = dict(default_data, **other)
+    else:
+        data = default_data
+
+    particles = f.create_group(f"PartType{part_type}")
+
+    for name, value in data.items():
+        particles.create_dataset(name, data=value)
+
+    return
+
diff --git a/src/hydro/Gadget2/hydro_iact.h b/src/hydro/Gadget2/hydro_iact.h
index b7b87e2224b78d18db751023db2083599d82b901..b7fa7dd4aa00249d009037961888c89abc0d0313 100644
--- a/src/hydro/Gadget2/hydro_iact.h
+++ b/src/hydro/Gadget2/hydro_iact.h
@@ -659,9 +659,9 @@ runner_iact_nonsym_1_vec_force(
     vector viz, vector pirho, vector grad_hi, vector piPOrho2, vector balsara_i,
     vector ci, float *Vjx, float *Vjy, float *Vjz, float *Pjrho, float *Grad_hj,
     float *PjPOrho2, float *Balsara_j, float *Cj, float *Mj, vector hi_inv,
-    vector hj_inv, const float a, const float H, vector *a_hydro_xSum, vector *a_hydro_ySum,
-    vector *a_hydro_zSum, vector *h_dtSum, vector *v_sigSum,
-    vector *entropy_dtSum, mask_t mask) {
+    vector hj_inv, const float a, const float H, vector *a_hydro_xSum,
+    vector *a_hydro_ySum, vector *a_hydro_zSum, vector *h_dtSum,
+    vector *v_sigSum, vector *entropy_dtSum, mask_t mask) {
 
 #ifdef WITH_VECTORIZATION
 
@@ -721,13 +721,16 @@ runner_iact_nonsym_1_vec_force(
   dvz.v = vec_sub(viz.v, vjz.v);
 
   /* Compute dv dot r. */
-  dvdr.v = vec_fma(dvx.v, dx->v, vec_fma(dvy.v, dy->v, vec_fma(dvz.v, dz->v, vec_mul(v_a2_Hubble.v, r2->v))));
+  dvdr.v =
+      vec_fma(dvx.v, dx->v,
+              vec_fma(dvy.v, dy->v,
+                      vec_fma(dvz.v, dz->v, vec_mul(v_a2_Hubble.v, r2->v))));
 
   /* Compute the relative velocity. (This is 0 if the particles move away from
    * each other and negative otherwise) */
   omega_ij.v = vec_fmin(dvdr.v, vec_setzero());
-  mu_ij.v =
-      vec_mul(v_fac_mu.v, vec_mul(ri.v, omega_ij.v)); /* This is 0 or negative */
+  mu_ij.v = vec_mul(v_fac_mu.v,
+                    vec_mul(ri.v, omega_ij.v)); /* This is 0 or negative */
 
   /* Compute signal velocity */
   v_sig.v = vec_fnma(vec_set1(3.f), mu_ij.v, vec_add(ci.v, cj.v));
@@ -790,9 +793,10 @@ runner_iact_nonsym_2_vec_force(
     vector viz, vector pirho, vector grad_hi, vector piPOrho2, vector balsara_i,
     vector ci, float *Vjx, float *Vjy, float *Vjz, float *Pjrho, float *Grad_hj,
     float *PjPOrho2, float *Balsara_j, float *Cj, float *Mj, vector hi_inv,
-    float *Hj_inv, const float a, const float H, vector *a_hydro_xSum, vector *a_hydro_ySum,
-    vector *a_hydro_zSum, vector *h_dtSum, vector *v_sigSum,
-    vector *entropy_dtSum, mask_t mask, mask_t mask_2, short mask_cond) {
+    float *Hj_inv, const float a, const float H, vector *a_hydro_xSum,
+    vector *a_hydro_ySum, vector *a_hydro_zSum, vector *h_dtSum,
+    vector *v_sigSum, vector *entropy_dtSum, mask_t mask, mask_t mask_2,
+    short mask_cond) {
 
 #ifdef WITH_VECTORIZATION
 
@@ -897,16 +901,20 @@ runner_iact_nonsym_2_vec_force(
   dvz_2.v = vec_sub(viz.v, vjz_2.v);
 
   /* Compute dv dot r. */
-  dvdr.v = vec_fma(dvx.v, dx.v, vec_fma(dvy.v, dy.v, vec_fma(dvz.v, dz.v, vec_mul(v_a2_Hubble.v, r2.v))));
-  dvdr_2.v = vec_fma(dvx_2.v, dx_2.v,
-                     vec_fma(dvy_2.v, dy_2.v, vec_fma(dvz_2.v, dz_2.v, vec_mul(v_a2_Hubble.v, r2_2.v))));
+  dvdr.v = vec_fma(
+      dvx.v, dx.v,
+      vec_fma(dvy.v, dy.v, vec_fma(dvz.v, dz.v, vec_mul(v_a2_Hubble.v, r2.v))));
+  dvdr_2.v = vec_fma(
+      dvx_2.v, dx_2.v,
+      vec_fma(dvy_2.v, dy_2.v,
+              vec_fma(dvz_2.v, dz_2.v, vec_mul(v_a2_Hubble.v, r2_2.v))));
 
   /* Compute the relative velocity. (This is 0 if the particles move away from
    * each other and negative otherwise) */
   omega_ij.v = vec_fmin(dvdr.v, vec_setzero());
   omega_ij_2.v = vec_fmin(dvdr_2.v, vec_setzero());
-  mu_ij.v =
-      vec_mul(v_fac_mu.v, vec_mul(ri.v, omega_ij.v)); /* This is 0 or negative */
+  mu_ij.v = vec_mul(v_fac_mu.v,
+                    vec_mul(ri.v, omega_ij.v)); /* This is 0 or negative */
   mu_ij_2.v = vec_mul(
       v_fac_mu.v, vec_mul(ri_2.v, omega_ij_2.v)); /* This is 0 or negative */
 
diff --git a/src/potential.h b/src/potential.h
index bcb3fd284021fba339ba494c90b81c91bd2ce72f..680d4e235fdf7a7666901f34a82f62feda4ae9bb 100644
--- a/src/potential.h
+++ b/src/potential.h
@@ -38,6 +38,10 @@
 #include "./potential/disc_patch/potential.h"
 #elif defined(EXTERNAL_POTENTIAL_SINE_WAVE)
 #include "./potential/sine_wave/potential.h"
+#elif defined(EXTERNAL_POTENTIAL_POINTMASS_RING)
+#include "./potential/point_mass_ring/potential.h"
+#elif defined(EXTERNAL_POTENTIAL_POINTMASS_SOFT)
+#include "./potential/point_mass_softened/potential.h"
 #else
 #error "Invalid choice of external potential"
 #endif
diff --git a/src/potential/point_mass_ring/potential.h b/src/potential/point_mass_ring/potential.h
new file mode 100644
index 0000000000000000000000000000000000000000..ebf047ea7c1f946536300f976713893e66295c59
--- /dev/null
+++ b/src/potential/point_mass_ring/potential.h
@@ -0,0 +1,226 @@
+/*******************************************************************************
+ * 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
+ *        based on Hopkins' central potential stuff (i.e. using a 'softened'
+ *        gravitational edge).
+ *
+ * We pass in the time for simulations where the potential evolves with time.
+ *
+ * @param time The current time.
+ * @param potential The properties 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 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 r = sqrtf(dx * dx + dy * dy + dz * dz);
+  const float rinv = 1.f / r;
+  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]);
+  float factor;
+
+  if (r < 0.175) {
+    // We need to flatten gravity as in SWIFT the timestepping is different
+    // than in GIZMO. This causes particles to become trapped close to the
+    // central point mass!
+    factor = 0.;
+  } else if (r < 0.35) {
+    factor = (8.16 * r * r) + 1.f - r / 0.35;
+  } else if (r > 2.1) {
+    factor = 1.f + (r - 2.1) / 0.1;
+  } else {
+    // 0.35 > r > 2.1
+    factor = 1.f;
+  }
+
+  const float dota_x = G_newton * potential->mass * rinv3 *
+                       (-g->v_full[0] + 3.f * rinv2 * drdv * dx) * factor;
+  const float dota_y = G_newton * potential->mass * rinv3 *
+                       (-g->v_full[1] + 3.f * rinv2 * drdv * dy) * factor;
+  const float dota_z = G_newton * potential->mass * rinv3 *
+                       (-g->v_full[2] + 3.f * rinv2 * drdv * dz) * factor;
+
+  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 r = sqrtf(dx * dx + dy * dy + dz * dz);
+  const float rinv = 1.f / r;
+  const float rinv2 = rinv * rinv;
+  const float rinv3 = rinv * rinv2;
+  float factor = 1.f;
+
+  if (r < 0.175) {
+    // We need to flatten gravity as in SWIFT the timestepping is different
+    // than in GIZMO. This causes particles to become trapped close to the
+    // central point mass!
+    factor = 0.;
+    printf("Help me, I'm trapped! (r = %f id = %lld)\n", r,
+           g->id_or_neg_offset);
+  } else if (r < 0.35) {
+    factor = (8.16 * r * r) + 1.f - r / 0.35;
+    printf("Factor is %f, r is %f \n", factor, r);
+  } else if (r > 2.1) {
+    factor = 1.f + (r - 2.1) / 0.1;
+  } else {
+    // 0.35 > r > 2.1
+    factor = 1.f;
+  }
+
+  g->a_grav[0] += -potential->mass * dx * rinv3 * factor;
+  g->a_grav[1] += -potential->mass * dy * rinv3 * factor;
+  g->a_grav[2] += -potential->mass * dz * rinv3 * factor;
+}
+
+/**
+ * @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/point_mass_softened/potential.h b/src/potential/point_mass_softened/potential.h
new file mode 100644
index 0000000000000000000000000000000000000000..83a79ea3cddbff37fdd70d58d70afcaf46f7bc0e
--- /dev/null
+++ b/src/potential/point_mass_softened/potential.h
@@ -0,0 +1,215 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2016 Tom Theuns (tom.theuns@durham.ac.uk)
+ *                    Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *                    Josh Borrow (joshua.borrow@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_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
+ *
+ * This file contains the softened central potential. This has a 'softening
+ * factor' that prevents the potential from becoming singular at the centre
+ * of the potential, i.e. it has the form
+ *
+ * $$ \phi \propto 1/(r^2 + \eta^2) $$
+ *
+ * where $\eta$ is some small value.
+ */
+struct external_potential {
+
+  /*! Position of the point mass */
+  double x, y, z;
+
+  /*! Mass */
+  double mass;
+
+  /*! Time-step condition pre-factor */
+  float timestep_mult;
+
+  /*! Potential softening */
+  float softening;
+};
+
+/**
+ * @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 dx = g->x[0] - potential->x;
+  const float dy = g->x[1] - potential->y;
+  const float dz = g->x[2] - potential->z;
+
+  const float softening2 = potential->softening * potential->softening;
+  const float r2 = dx * dx + dy * dy + dz * dz;
+  const float rinv = 1.f / sqrtf(r2);
+  const float rinv2_softened = 1.f / (r2 + softening2);
+
+  /* G * M / (r^2 + eta^2)^{3/2} */
+  const float GMr32 = phys_const->const_newton_G * potential->mass *
+                      sqrtf(rinv2_softened * rinv2_softened * rinv2_softened);
+
+  /* This is actually dr dot v */
+  const float drdv =
+      (dx) * (g->v_full[0]) + (dy) * (g->v_full[1]) + (dz) * (g->v_full[2]);
+
+  /* We want da/dt */
+  /* da/dt = -GM/(r^2 + \eta^2)^{3/2}  *
+   *         (\vec{v} - 3 \vec{r} \cdot \vec{v} \hat{r} /
+   *         (r^2 + \eta^2)) */
+  const float dota_x =
+      GMr32 * (3.f * drdv * dx * rinv2_softened * rinv - g->v_full[0]);
+  const float dota_y =
+      GMr32 * (3.f * drdv * dy * rinv2_softened * rinv - g->v_full[0]);
+  const float dota_z =
+      GMr32 * (3.f * drdv * dz * rinv2_softened * rinv - g->v_full[0]);
+
+  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 +
+                                 potential->softening * potential->softening);
+  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 +
+                                potential->softening * potential->softening);
+
+  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");
+  potential->softening =
+      parser_get_param_float(parameter_file, "PointMassPotential:softening");
+}
+
+/**
+ * @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, softening = %e.",
+      potential->x, potential->y, potential->z, potential->mass,
+      potential->timestep_mult, potential->softening);
+}
+
+#endif /* SWIFT_POTENTIAL_POINT_MASS_H */
diff --git a/src/runner_doiact_vec.c b/src/runner_doiact_vec.c
index c2b807e7061f4b8ac7c2066bb03f2a7c93d9610b..0daea0cf8a8d868ccb14f7d9b27f04aca5a677c8 100644
--- a/src/runner_doiact_vec.c
+++ b/src/runner_doiact_vec.c
@@ -1143,7 +1143,7 @@ void runner_doself2_force_vec(struct runner *r, struct cell *restrict c) {
   /* Cosmological terms */
   const float a = cosmo->a;
   const float H = cosmo->H;
-  
+
   /* Loop over the particles in the cell. */
   for (int pid = 0; pid < count; pid++) {
 
diff --git a/tests/testInteractions.c b/tests/testInteractions.c
index 2ded11b162e88380a4ad5c8ad23bb76b22478ec3..9c5dd36415970ff2a53220aa56cecba6fc5fe193 100644
--- a/tests/testInteractions.c
+++ b/tests/testInteractions.c
@@ -610,8 +610,9 @@ void test_force_interactions(struct part test_part, struct part *parts,
             (viz_vec), rhoi_vec, grad_hi_vec, pOrhoi2_vec, balsara_i_vec,
             ci_vec, &(vjxq[i]), &(vjyq[i]), &(vjzq[i]), &(rhojq[i]),
             &(grad_hjq[i]), &(pOrhoj2q[i]), &(balsarajq[i]), &(cjq[i]),
-            &(mjq[i]), hi_inv_vec, &(hj_invq[i]), a, H, &a_hydro_xSum, &a_hydro_ySum,
-            &a_hydro_zSum, &h_dtSum, &v_sigSum, &entropy_dtSum, mask, mask2, 0);
+            &(mjq[i]), hi_inv_vec, &(hj_invq[i]), a, H, &a_hydro_xSum,
+            &a_hydro_ySum, &a_hydro_zSum, &h_dtSum, &v_sigSum, &entropy_dtSum,
+            mask, mask2, 0);
       } else { /* Only use one vector for interaction. */
 
         vector my_r2, my_dx, my_dy, my_dz, hj, hj_inv;
@@ -626,7 +627,7 @@ void test_force_interactions(struct part test_part, struct part *parts,
             &my_r2, &my_dx, &my_dy, &my_dz, vix_vec, viy_vec, viz_vec, rhoi_vec,
             grad_hi_vec, pOrhoi2_vec, balsara_i_vec, ci_vec, &(vjxq[i]),
             &(vjyq[i]), &(vjzq[i]), &(rhojq[i]), &(grad_hjq[i]), &(pOrhoj2q[i]),
-            &(balsarajq[i]), &(cjq[i]), &(mjq[i]), hi_inv_vec, hj_inv, a, H, 
+            &(balsarajq[i]), &(cjq[i]), &(mjq[i]), hi_inv_vec, hj_inv, a, H,
             &a_hydro_xSum, &a_hydro_ySum, &a_hydro_zSum, &h_dtSum, &v_sigSum,
             &entropy_dtSum, mask);
       }