diff --git a/configure.ac b/configure.ac
index 3db764605d03b07138d5622826b7bc8d1cf1849c..4e920c2b8d6ef08091942f22f31a7ca25aff0185 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1449,7 +1449,7 @@ if test "x$with_lustreapi" != "xno"; then
       LUSTREAPI_LIBS="-llustreapi"
       LUSTREAPI_INCS=""
    fi
-   AC_CHECK_LIB([lustreapi], [llapi_obd_statfs], [have_lustreapi="yes"], 
+   AC_CHECK_LIB([lustreapi], [llapi_obd_statfs], [have_lustreapi="yes"],
                 [have_lustreapi="no"],[$LUSTREAPI_LIBS])
 
    if test "$have_lustreapi" = "yes"; then
@@ -2223,7 +2223,7 @@ fi
 # Hydro scheme.
 AC_ARG_WITH([hydro],
    [AS_HELP_STRING([--with-hydro=<scheme>],
-      [Hydro dynamics to use @<:@gadget2, minimal, pressure-entropy, pressure-energy, pressure-energy-monaghan, phantom, gizmo-mfv, gizmo-mfm, shadowswift, planetary, sphenix, gasoline, anarchy-pu default: sphenix@:>@]
+      [Hydro dynamics to use @<:@gadget2, minimal, pressure-entropy, pressure-energy, pressure-energy-monaghan, phantom, gizmo-mfv, gizmo-mfm, shadowswift, planetary, remix, sphenix, gasoline, anarchy-pu default: sphenix@:>@]
    )],
    [with_hydro="$withval"],
    [with_hydro="sphenix"]
@@ -2270,6 +2270,9 @@ case "$with_hydro" in
    planetary)
       AC_DEFINE([PLANETARY_SPH], [1], [Planetary SPH])
    ;;
+   remix)
+      AC_DEFINE([REMIX_SPH], [1], [REMIX SPH])
+   ;;
    sphenix)
       AC_DEFINE([SPHENIX_SPH], [1], [SPHENIX SPH])
    ;;
diff --git a/examples/Planetary/KelvinHelmholtz_IdealGas_LargeDensityContrast_3D/kelvin_helmholtz.yml b/examples/Planetary/KelvinHelmholtz_IdealGas_LargeDensityContrast_3D/kelvin_helmholtz.yml
index e1ab89fd99c42cbfbb0fcc57eabf1c97f3462e93..a7f6cbbd388d11026a01bb7153786b1ab907b39e 100644
--- a/examples/Planetary/KelvinHelmholtz_IdealGas_LargeDensityContrast_3D/kelvin_helmholtz.yml
+++ b/examples/Planetary/KelvinHelmholtz_IdealGas_LargeDensityContrast_3D/kelvin_helmholtz.yml
@@ -34,7 +34,7 @@ InitialConditions:
   periodic:   1
   
 Scheduler:
-    max_top_level_cells:      50         # Maximal number of top-level cells in any dimension.
+    max_top_level_cells:      80         # Maximal number of top-level cells in any dimension.
     
 # Parameters related to the equation of state
 EoS:
diff --git a/src/debug.c b/src/debug.c
index 0b1538b5c4c72e1b292fb29e57a6526c38a4ff7a..2db8f69dff21c88ddd3f5ef4d9639e02ce394d89 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -74,6 +74,8 @@
 #include "./hydro/Shadowswift/hydro_debug.h"
 #elif defined(PLANETARY_SPH)
 #include "./hydro/Planetary/hydro_debug.h"
+#elif defined(REMIX_SPH)
+#include "./hydro/REMIX/hydro_debug.h"
 #elif defined(SPHENIX_SPH)
 #include "./hydro/SPHENIX/hydro_debug.h"
 #elif defined(GASOLINE_SPH)
diff --git a/src/hydro.h b/src/hydro.h
index 048be9f314a22f14246cd39434dfcbb66ec610bf..c82c402e81f2937f9beaf389b4a0a68506236dfc 100644
--- a/src/hydro.h
+++ b/src/hydro.h
@@ -70,6 +70,10 @@
 #include "./hydro/Planetary/hydro.h"
 #include "./hydro/Planetary/hydro_iact.h"
 #define SPH_IMPLEMENTATION "Minimal version of SPH with multiple materials"
+#elif defined(REMIX_SPH)
+#include "./hydro/REMIX/hydro.h"
+#include "./hydro/REMIX/hydro_iact.h"
+#define SPH_IMPLEMENTATION "REMIX (Sandnes+ 2025)"
 #elif defined(SPHENIX_SPH)
 #include "./hydro/SPHENIX/hydro.h"
 #include "./hydro/SPHENIX/hydro_iact.h"
@@ -89,9 +93,9 @@
 
 /* Check whether this scheme implements the density checks */
 #ifdef SWIFT_HYDRO_DENSITY_CHECKS
-#if !defined(SPHENIX_SPH) && !defined(PLANETARY_SPH)
+#if !defined(SPHENIX_SPH) && !defined(PLANETARY_SPH) && !defined(REMIX_SPH)
 #error \
-    "Can only use the hydro brute-force density checks with the SPHENIX or PLANETARY hydro schemes."
+    "Can only use the hydro brute-force density checks with the SPHENIX or PLANETARY or REMIX hydro schemes."
 #endif
 #endif
 
diff --git a/src/hydro/REMIX/hydro.h b/src/hydro/REMIX/hydro.h
new file mode 100644
index 0000000000000000000000000000000000000000..bb5517e78a103a3fce777b20db115a4826a49219
--- /dev/null
+++ b/src/hydro/REMIX/hydro.h
@@ -0,0 +1,981 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Thomas Sandnes (thomas.d.sandnes@durham.ac.uk)
+ *               2024 Jacob Kegerreis (jacob.kegerreis@durham.ac.uk)
+ *               2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_REMIX_HYDRO_H
+#define SWIFT_REMIX_HYDRO_H
+
+/**
+ * @file REMIX/hydro.h
+ * @brief REMIX implementation of SPH (Sandnes et al. 2025)
+ */
+
+#include "adiabatic_index.h"
+#include "approx_math.h"
+#include "cosmology.h"
+#include "debug.h"
+#include "dimension.h"
+#include "entropy_floor.h"
+#include "equation_of_state.h"
+#include "hydro_kernels.h"
+#include "hydro_parameters.h"
+#include "hydro_properties.h"
+#include "hydro_space.h"
+#include "hydro_visc_difn.h"
+#include "kernel_hydro.h"
+#include "minmax.h"
+#include "pressure_floor.h"
+
+/**
+ * @brief Returns the comoving internal energy of a particle at the last
+ * time the particle was kicked.
+ *
+ * For implementations where the main thermodynamic variable
+ * is not internal energy, this function computes the internal
+ * energy from the thermodynamic variable.
+ *
+ * @param p The particle of interest
+ * @param xp The extended data of the particle of interest.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_internal_energy(const struct part *restrict p,
+                                   const struct xpart *restrict xp) {
+
+  return xp->u_full;
+}
+
+/**
+ * @brief Returns the physical internal energy of a particle at the last
+ * time the particle was kicked.
+ *
+ * For implementations where the main thermodynamic variable
+ * is not internal energy, this function computes the internal
+ * energy from the thermodynamic variable and converts it to
+ * physical coordinates.
+ *
+ * @param p The particle of interest.
+ * @param xp The extended data of the particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_internal_energy(const struct part *restrict p,
+                                   const struct xpart *restrict xp,
+                                   const struct cosmology *cosmo) {
+
+  return xp->u_full * cosmo->a_factor_internal_energy;
+}
+
+/**
+ * @brief Returns the comoving internal energy of a particle drifted to the
+ * current time.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_drifted_comoving_internal_energy(const struct part *restrict p) {
+
+  return p->u;
+}
+
+/**
+ * @brief Returns the physical internal energy of a particle drifted to the
+ * current time.
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_drifted_physical_internal_energy(const struct part *restrict p,
+                                           const struct cosmology *cosmo) {
+
+  return p->u * cosmo->a_factor_internal_energy;
+}
+
+/**
+ * @brief Returns the comoving pressure of a particle
+ *
+ * Computes the pressure based on the particle's properties.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_pressure(
+    const struct part *restrict p) {
+
+  return gas_pressure_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+}
+
+/**
+ * @brief Returns the physical pressure of a particle
+ *
+ * Computes the pressure based on the particle's properties and
+ * convert it to physical coordinates.
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_pressure(
+    const struct part *restrict p, const struct cosmology *cosmo) {
+
+  return cosmo->a_factor_pressure *
+         gas_pressure_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+}
+
+/**
+ * @brief Returns the comoving entropy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not entropy, this function computes the entropy from
+ * the thermodynamic variable.
+ *
+ * @param p The particle of interest
+ * @param xp The extended data of the particle of interest.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_entropy(
+    const struct part *restrict p, const struct xpart *restrict xp) {
+
+  return gas_entropy_from_internal_energy(p->rho_evol, xp->u_full, p->mat_id);
+}
+
+/**
+ * @brief Returns the physical entropy of a particle
+ *
+ * For implementations where the main thermodynamic variable
+ * is not entropy, this function computes the entropy from
+ * the thermodynamic variable and converts it to
+ * physical coordinates.
+ *
+ * @param p The particle of interest
+ * @param xp The extended data of the particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_entropy(
+    const struct part *restrict p, const struct xpart *restrict xp,
+    const struct cosmology *cosmo) {
+
+  /* Note: no cosmological conversion required here with our choice of
+   * coordinates. */
+  return gas_entropy_from_internal_energy(p->rho_evol, xp->u_full, p->mat_id);
+}
+
+/**
+ * @brief Returns the comoving entropy of a particle drifted to the
+ * current time.
+ *
+ * @param p The particle of interest.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_drifted_comoving_entropy(const struct part *restrict p) {
+
+  return gas_entropy_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+}
+
+/**
+ * @brief Returns the physical entropy of a particle drifted to the
+ * current time.
+ *
+ * @param p The particle of interest.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_drifted_physical_entropy(const struct part *restrict p,
+                                   const struct cosmology *cosmo) {
+
+  /* Note: no cosmological conversion required here with our choice of
+   * coordinates. */
+  return gas_entropy_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+}
+
+/**
+ * @brief Returns the comoving sound speed of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_soundspeed(const struct part *restrict p) {
+
+  return p->force.soundspeed;
+}
+
+/**
+ * @brief Returns the physical sound speed of a particle
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_soundspeed(const struct part *restrict p,
+                              const struct cosmology *cosmo) {
+
+  return cosmo->a_factor_sound_speed * p->force.soundspeed;
+}
+
+/**
+ * @brief Returns the comoving density of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_comoving_density(
+    const struct part *restrict p) {
+
+  return p->rho;
+}
+
+/**
+ * @brief Returns the comoving density of a particle.
+ *
+ * @param p The particle of interest
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_physical_density(
+    const struct part *restrict p, const struct cosmology *cosmo) {
+
+  return cosmo->a3_inv * p->rho;
+}
+
+/**
+ * @brief Returns the mass of a particle
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_mass(
+    const struct part *restrict p) {
+
+  return p->mass;
+}
+
+/**
+ * @brief Sets the mass of a particle
+ *
+ * @param p The particle of interest
+ * @param m The mass to set.
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_mass(
+    struct part *restrict p, float m) {
+
+  p->mass = m;
+}
+
+/**
+ * @brief Returns the time derivative of internal energy of a particle
+ *
+ * We assume a constant density.
+ *
+ * @param p The particle of interest
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_comoving_internal_energy_dt(const struct part *restrict p) {
+
+  return p->u_dt;
+}
+
+/**
+ * @brief Returns the time derivative of internal energy of a particle
+ *
+ * We assume a constant density.
+ *
+ * @param p The particle of interest
+ * @param cosmo Cosmology data structure
+ */
+__attribute__((always_inline)) INLINE static float
+hydro_get_physical_internal_energy_dt(const struct part *restrict p,
+                                      const struct cosmology *cosmo) {
+
+  return p->u_dt * cosmo->a_factor_internal_energy;
+}
+
+/**
+ * @brief Returns the time derivative of internal energy of a particle
+ *
+ * We assume a constant density.
+ *
+ * @param p The particle of interest.
+ * @param du_dt The new time derivative of the internal energy.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_set_comoving_internal_energy_dt(struct part *restrict p, float du_dt) {
+
+  p->u_dt = du_dt;
+}
+
+/**
+ * @brief Returns the time derivative of internal energy of a particle
+ *
+ * We assume a constant density.
+ *
+ * @param p The particle of interest.
+ * @param cosmo Cosmology data structure
+ * @param du_dt The new time derivative of the internal energy.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_set_physical_internal_energy_dt(struct part *restrict p,
+                                      const struct cosmology *cosmo,
+                                      float du_dt) {
+
+  p->u_dt = du_dt / cosmo->a_factor_internal_energy;
+}
+
+/**
+ * @brief Sets the physical entropy of a particle
+ *
+ * @param p The particle of interest.
+ * @param xp The extended particle data.
+ * @param cosmo Cosmology data structure
+ * @param entropy The physical entropy
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_physical_entropy(
+    struct part *p, struct xpart *xp, const struct cosmology *cosmo,
+    const float entropy) {
+
+  /* Note there is no conversion from physical to comoving entropy */
+  const float comoving_entropy = entropy;
+  xp->u_full = gas_internal_energy_from_entropy(p->rho_evol, comoving_entropy,
+                                                p->mat_id);
+}
+
+/**
+ * @brief Sets the physical internal energy of a particle
+ *
+ * @param p The particle of interest.
+ * @param xp The extended particle data.
+ * @param cosmo Cosmology data structure
+ * @param u The physical internal energy
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_set_physical_internal_energy(struct part *p, struct xpart *xp,
+                                   const struct cosmology *cosmo,
+                                   const float u) {
+
+  xp->u_full = u / cosmo->a_factor_internal_energy;
+}
+
+/**
+ * @brief Sets the drifted physical internal energy of a particle
+ *
+ * @param p The particle of interest.
+ * @param cosmo Cosmology data structure
+ * @param u The physical internal energy
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_set_drifted_physical_internal_energy(struct part *p,
+                                           const struct cosmology *cosmo,
+                                           const float u) {
+
+  p->u = u / cosmo->a_factor_internal_energy;
+
+  /* Now recompute the extra quantities */
+
+  /* Compute the sound speed */
+  const float pressure =
+      gas_pressure_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+  const float soundspeed = hydro_get_comoving_soundspeed(p);
+
+  /* Update variables. */
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
+
+  p->force.v_sig = max(p->force.v_sig, 2.f * soundspeed);
+}
+
+/**
+ * @brief Update the value of the viscosity alpha for the scheme.
+ *
+ * @param p the particle of interest
+ * @param alpha the new value for the viscosity coefficient.
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_viscosity_alpha(
+    struct part *restrict p, float alpha) {
+  /* This scheme has fixed alpha */
+}
+
+/**
+ * @brief Update the value of the diffusive coefficients to the
+ *        feedback reset value for the scheme.
+ *
+ * @param p the particle of interest
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_diffusive_feedback_reset(struct part *restrict p) {
+  /* This scheme has fixed alpha */
+}
+
+/**
+ * @brief Computes the hydro time-step of a given particle
+ *
+ * This function returns the time-step of a particle given its hydro-dynamical
+ * state. A typical time-step calculation would be the use of the CFL condition.
+ *
+ * @param p Pointer to the particle data
+ * @param xp Pointer to the extended particle data
+ * @param hydro_properties The SPH parameters
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static float hydro_compute_timestep(
+    const struct part *restrict p, const struct xpart *restrict xp,
+    const struct hydro_props *restrict hydro_properties,
+    const struct cosmology *restrict cosmo) {
+
+  const float CFL_condition = hydro_properties->CFL_condition;
+
+  /* CFL condition */
+  const float dt_cfl = 2.f * kernel_gamma * CFL_condition * cosmo->a * p->h /
+                       (cosmo->a_factor_sound_speed * p->force.v_sig);
+
+  return dt_cfl;
+}
+
+/**
+ * @brief returns the signal velocity
+ *
+ * @brief p  the particle
+ */
+__attribute__((always_inline)) INLINE static float hydro_get_signal_velocity(
+    const struct part *restrict p) {
+
+  return p->force.v_sig;
+}
+
+/**
+ * @brief Does some extra hydro operations once the actual physical time step
+ * for the particle is known.
+ *
+ * @param p The particle to act upon.
+ * @param dt Physical time step of the particle during the next step.
+ */
+__attribute__((always_inline)) INLINE static void hydro_timestep_extra(
+    struct part *p, float dt) {}
+
+/**
+ * @brief Prepares a particle for the density calculation.
+ *
+ * Zeroes all the relevant arrays in preparation for the sums taking place in
+ * the various density loop over neighbours. Typically, all fields of the
+ * density sub-structure of a particle get zeroed in here.
+ *
+ * @param p The particle to act upon
+ * @param hs #hydro_space containing hydro specific space information.
+ */
+__attribute__((always_inline)) INLINE static void hydro_init_part(
+    struct part *restrict p, const struct hydro_space *hs) {
+
+  p->density.wcount = 0.f;
+  p->density.wcount_dh = 0.f;
+  p->rho = 0.f;
+  p->density.rho_dh = 0.f;
+
+  hydro_init_part_extra_kernel(p);
+}
+
+/**
+ * @brief Finishes the density calculation.
+ *
+ * Multiplies the density and number of neighbours by the appropiate constants
+ * and add the self-contribution term.
+ * Additional quantities such as velocity gradients will also get the final
+ * terms added to them here.
+ *
+ * Also adds/multiplies the cosmological terms if need be.
+ *
+ * @param p The particle to act upon
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_density(
+    struct part *restrict p, const struct cosmology *cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                       /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv);       /* 1/h^d */
+  const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */
+
+  /* Final operation on the density (add self-contribution). */
+  p->rho += p->mass * kernel_root;
+  p->density.rho_dh -= hydro_dimension * p->mass * kernel_root;
+  p->density.wcount += kernel_root;
+  p->density.wcount_dh -= hydro_dimension * kernel_root;
+
+  /* Finish the calculation by inserting the missing h-factors */
+  p->rho *= h_inv_dim;
+  p->density.rho_dh *= h_inv_dim_plus_one;
+  p->density.wcount *= h_inv_dim;
+  p->density.wcount_dh *= h_inv_dim_plus_one;
+
+  hydro_end_density_extra_kernel(p);
+}
+
+/**
+ * @brief Sets all particle fields to sensible values when the #part has 0 ngbs.
+ *
+ * In the desperate case where a particle has no neighbours (likely because
+ * of the h_max ceiling), set the particle fields to something sensible to avoid
+ * NaNs in the next calculations.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_part_has_no_neighbours(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo) {
+
+  /* Some smoothing length multiples. */
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+
+  /* Re-set problematic values */
+  p->rho = p->mass * kernel_root * h_inv_dim;
+  p->density.wcount = kernel_root * h_inv_dim;
+  p->density.rho_dh = 0.f;
+  p->density.wcount_dh = 0.f;
+
+  p->is_h_max = 1;
+  p->m0 = p->mass * kernel_root * h_inv_dim / p->rho_evol;
+}
+
+/**
+ * @brief Prepare a particle for the gradient calculation.
+ *
+ * This function is called after the density loop and before the gradient loop.
+ *
+ * We use it to set the physical timestep for the particle and to copy the
+ * actual velocities, which we need to boost our interfaces during the flux
+ * calculation. We also initialize the variables used for the time step
+ * calculation.
+ *
+ * @param p The particle to act upon.
+ * @param xp The extended particle data to act upon.
+ * @param cosmo The cosmological model.
+ * @param hydro_props Hydrodynamic properties.
+ */
+__attribute__((always_inline)) INLINE static void hydro_prepare_gradient(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo, const struct hydro_props *hydro_props,
+    const struct pressure_floor_props *pressure_floor) {
+
+  /* Set p->is_h_max = 1 if smoothing length is h_max */
+  if (p->h > 0.999f * hydro_props->h_max) {
+    p->is_h_max = 1;
+  } else {
+    p->is_h_max = 0;
+  }
+
+  hydro_prepare_gradient_extra_kernel(p);
+  hydro_prepare_gradient_extra_visc_difn(p);
+}
+
+/**
+ * @brief Resets the variables that are required for a gradient calculation.
+ *
+ * This function is called after hydro_prepare_gradient.
+ *
+ * @param p The particle to act upon.
+ * @param xp The extended particle data to act upon.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_reset_gradient(
+    struct part *restrict p) {}
+
+/**
+ * @brief Finishes the gradient calculation.
+ *
+ * Just a wrapper around hydro_gradients_finalize, which can be an empty method,
+ * in which case no gradients are used.
+ *
+ * This method also initializes the force loop variables.
+ *
+ * @param p The particle to act upon.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_gradient(
+    struct part *p) {
+
+  hydro_end_gradient_extra_kernel(p);
+  hydro_end_gradient_extra_visc_difn(p);
+
+  /* Set the density to be used in the force loop to be the evolved density */
+  p->rho = p->rho_evol;
+}
+
+/**
+ * @brief Prepare a particle for the force calculation.
+ *
+ * This function is called in the ghost task to convert some quantities coming
+ * from the density loop over neighbours into quantities ready to be used in the
+ * force loop over neighbours. Quantities are typically read from the density
+ * sub-structure and written to the force sub-structure.
+ * Examples of calculations done here include the calculation of viscosity term
+ * constants, thermal conduction terms, hydro conversions, etc.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ * @param cosmo The current cosmological model.
+ * @param hydro_props Hydrodynamic properties.
+ * @param dt_alpha The time-step used to evolve non-cosmological quantities such
+ *                 as the artificial viscosity.
+ * @param dt_therm The time-step used to evolve hydrodynamical quantities.
+ */
+__attribute__((always_inline)) INLINE static void hydro_prepare_force(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo, const struct hydro_props *hydro_props,
+    const struct pressure_floor_props *pressure_floor, const float dt_alpha,
+    const float dt_therm) {
+
+  hydro_prepare_force_extra_kernel(p);
+
+#ifdef PLANETARY_FIXED_ENTROPY
+  /* Override the internal energy to satisfy the fixed entropy */
+  p->u = gas_internal_energy_from_entropy(p->rho_evol, p->s_fixed, p->mat_id);
+  xp->u_full = p->u;
+#endif
+
+  /* Compute the sound speed */
+  const float soundspeed =
+      gas_soundspeed_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+
+  const float div_v = p->dv_norm_kernel[0][0] + p->dv_norm_kernel[1][1] +
+                      p->dv_norm_kernel[2][2];
+  float curl_v[3];
+  curl_v[0] = p->dv_norm_kernel[1][2] - p->dv_norm_kernel[2][1];
+  curl_v[1] = p->dv_norm_kernel[2][0] - p->dv_norm_kernel[0][2];
+  curl_v[2] = p->dv_norm_kernel[0][1] - p->dv_norm_kernel[1][0];
+  const float mod_curl_v = sqrtf(curl_v[0] * curl_v[0] + curl_v[1] * curl_v[1] +
+                                 curl_v[2] * curl_v[2]);
+
+  /* Balsara switch using normalised kernel gradients (Sandnes+2025 Eqn. 34 with
+   * velocity gradients calculated by Eqn. 35) */
+  float balsara;
+  if (div_v == 0.f) {
+    balsara = 0.f;
+  } else {
+    balsara = fabsf(div_v) /
+              (fabsf(div_v) + mod_curl_v + 0.0001f * soundspeed / p->h);
+  }
+
+  /* Compute the pressure */
+  const float pressure =
+      gas_pressure_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
+  p->force.balsara = balsara;
+  /* Set eta_crit for arificial viscosity and diffusion slope limiters */
+  p->force.eta_crit = 1.f / hydro_props->eta_neighbours;
+}
+
+/**
+ * @brief Reset acceleration fields of a particle
+ *
+ * Resets all hydro acceleration and time derivative fields in preparation
+ * for the sums taking  place in the various force tasks.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_reset_acceleration(
+    struct part *restrict p) {
+
+  /* Reset the acceleration. */
+  p->a_hydro[0] = 0.0f;
+  p->a_hydro[1] = 0.0f;
+  p->a_hydro[2] = 0.0f;
+
+  /* Reset the time derivatives. */
+  p->u_dt = 0.0f;
+  p->drho_dt = 0.0f;
+  p->force.h_dt = 0.0f;
+  p->force.v_sig = p->force.soundspeed;
+}
+
+/**
+ * @brief Sets the values to be predicted in the drifts to their values at a
+ * kick time
+ *
+ * @param p The particle.
+ * @param xp The extended data of this particle.
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_reset_predicted_values(
+    struct part *restrict p, const struct xpart *restrict xp,
+    const struct cosmology *cosmo,
+    const struct pressure_floor_props *pressure_floor) {
+
+  /* Re-set the predicted velocities */
+  p->v[0] = xp->v_full[0];
+  p->v[1] = xp->v_full[1];
+  p->v[2] = xp->v_full[2];
+
+  /* Re-set the internal energy */
+  p->u = xp->u_full;
+
+  /* Re-set the density */
+  p->rho = xp->rho_evol_full;
+  p->rho_evol = xp->rho_evol_full;
+
+  /* Compute the pressure */
+  const float pressure =
+      gas_pressure_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+
+  /* Compute the sound speed */
+  const float soundspeed =
+      gas_soundspeed_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
+
+  p->force.v_sig = max(p->force.v_sig, 2.f * soundspeed);
+}
+
+/**
+ * @brief Predict additional particle fields forward in time when drifting
+ *
+ * Additional hydrodynamic quantites are drifted forward in time here. These
+ * include thermal quantities (thermal energy or total energy or entropy, ...).
+ *
+ * Note the different time-step sizes used for the different quantities as they
+ * include cosmological factors.
+ *
+ * @param p The particle.
+ * @param xp The extended data of the particle.
+ * @param dt_drift The drift time-step for positions.
+ * @param dt_therm The drift time-step for thermal quantities.
+ * @param dt_kick_grav The time-step for gravity quantities.
+ * @param cosmo The cosmological model.
+ * @param hydro_props The constants used in the scheme
+ * @param floor_props The properties of the entropy floor.
+ */
+__attribute__((always_inline)) INLINE static void hydro_predict_extra(
+    struct part *restrict p, const struct xpart *restrict xp, float dt_drift,
+    float dt_therm, float dt_kick_grav, const struct cosmology *cosmo,
+    const struct hydro_props *hydro_props,
+    const struct entropy_floor_properties *floor_props,
+    const struct pressure_floor_props *pressure_floor) {
+
+  /* Predict the internal energy and density */
+  p->u += p->u_dt * dt_therm;
+  p->rho_evol += p->drho_dt * dt_therm;
+
+  /* compute minimum SPH quantities */
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+  const float floor_rho = p->mass * kernel_root * h_inv_dim;
+  p->rho_evol = max(p->rho_evol, floor_rho);
+  p->rho = p->rho_evol;
+
+  /* Check against absolute minimum */
+  const float min_u =
+      hydro_props->minimal_internal_energy / cosmo->a_factor_internal_energy;
+
+  p->u = max(p->u, min_u);
+
+  /* Predict smoothing length */
+  const float w1 = p->force.h_dt * h_inv * dt_drift;
+  if (fabsf(w1) < 0.2f)
+    p->h *= approx_expf(w1); /* 4th order expansion of exp(w) */
+  else
+    p->h *= expf(w1);
+
+  const float floor_u = FLT_MIN;
+  p->u = max(p->u, floor_u);
+
+  /* Compute the new pressure */
+  const float pressure =
+      gas_pressure_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+
+  /* Compute the new sound speed */
+  const float soundspeed =
+      gas_soundspeed_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
+
+  p->force.v_sig = max(p->force.v_sig, 2.f * soundspeed);
+}
+
+/**
+ * @brief Finishes the force calculation.
+ *
+ * Multiplies the force and accelerations by the appropiate constants
+ * and add the self-contribution term. In most cases, there is little
+ * to do here.
+ *
+ * Cosmological terms are also added/multiplied here.
+ *
+ * @param p The particle to act upon
+ * @param cosmo The current cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_end_force(
+    struct part *restrict p, const struct cosmology *cosmo) {
+
+  p->force.h_dt *= p->h * hydro_dimension_inv;
+}
+
+/**
+ * @brief Kick the additional variables
+ *
+ * Additional hydrodynamic quantites are kicked forward in time here. These
+ * include thermal quantities (thermal energy or total energy or entropy, ...).
+ *
+ * @param p The particle to act upon.
+ * @param xp The particle extended data to act upon.
+ * @param dt_therm The time-step for this kick (for thermodynamic quantities).
+ * @param dt_grav The time-step for this kick (for gravity quantities).
+ * @param dt_grav_mesh The time-step for this kick (mesh gravity).
+ * @param dt_hydro The time-step for this kick (for hydro quantities).
+ * @param dt_kick_corr The time-step for this kick (for gravity corrections).
+ * @param cosmo The cosmological model.
+ * @param hydro_props The constants used in the scheme
+ * @param floor_props The properties of the entropy floor.
+ */
+__attribute__((always_inline)) INLINE static void hydro_kick_extra(
+    struct part *restrict p, struct xpart *restrict xp, float dt_therm,
+    float dt_grav, float dt_grav_mesh, float dt_hydro, float dt_kick_corr,
+    const struct cosmology *cosmo, const struct hydro_props *hydro_props,
+    const struct entropy_floor_properties *floor_props) {
+
+  /* Integrate the internal energy forward in time */
+  const float delta_u = p->u_dt * dt_therm;
+
+  /* Do not decrease the energy by more than a factor of 2*/
+  xp->u_full = max(xp->u_full + delta_u, 0.5f * xp->u_full);
+
+  /* Check against absolute minimum */
+  const float min_u =
+      hydro_props->minimal_internal_energy / cosmo->a_factor_internal_energy;
+  const float floor_u = FLT_MIN;
+
+  /* Take highest of both limits */
+  const float energy_min = max(min_u, floor_u);
+
+  if (xp->u_full < energy_min) {
+    xp->u_full = energy_min;
+    p->u_dt = 0.f;
+  }
+
+  /* Integrate the density forward in time */
+  const float delta_rho = p->drho_dt * dt_therm;
+
+  /* Do not decrease the density by more than a factor of 2*/
+  xp->rho_evol_full =
+      max(xp->rho_evol_full + delta_rho, 0.5f * xp->rho_evol_full);
+
+  /* Minimum SPH density */
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                 /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */
+  const float floor_rho = p->mass * kernel_root * h_inv_dim;
+  if (xp->rho_evol_full < floor_rho) {
+    xp->rho_evol_full = floor_rho;
+    p->drho_dt = 0.f;
+  }
+}
+
+/**
+ * @brief Converts hydro quantity of a particle at the start of a run
+ *
+ * This function is called once at the end of the engine_init_particle()
+ * routine (at the start of a calculation) after the densities of
+ * particles have been computed.
+ * This can be used to convert internal energy into entropy for instance.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle to act upon
+ * @param cosmo The cosmological model.
+ */
+__attribute__((always_inline)) INLINE static void hydro_convert_quantities(
+    struct part *restrict p, struct xpart *restrict xp,
+    const struct cosmology *cosmo, const struct hydro_props *hydro_props,
+    const struct pressure_floor_props *pressure_floor) {
+
+  /* Compute the pressure */
+  const float pressure =
+      gas_pressure_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+
+  /* Compute the sound speed */
+  const float soundspeed =
+      gas_soundspeed_from_internal_energy(p->rho_evol, p->u, p->mat_id);
+
+  p->force.pressure = pressure;
+  p->force.soundspeed = soundspeed;
+}
+
+/**
+ * @brief Initialises the particles for the first time
+ *
+ * This function is called only once just after the ICs have been
+ * read in to do some conversions or assignments between the particle
+ * and extended particle fields.
+ *
+ * @param p The particle to act upon
+ * @param xp The extended particle data to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_first_init_part(
+    struct part *restrict p, struct xpart *restrict xp) {
+
+  p->time_bin = 0;
+  xp->v_full[0] = p->v[0];
+  xp->v_full[1] = p->v[1];
+  xp->v_full[2] = p->v[2];
+  xp->u_full = p->u;
+
+  p->rho_evol = p->rho;
+  xp->rho_evol_full = p->rho_evol;
+
+  hydro_reset_acceleration(p);
+  hydro_init_part(p, NULL);
+}
+
+/**
+ * @brief Overwrite the initial internal energy of a particle.
+ *
+ * Note that in the cases where the thermodynamic variable is not
+ * internal energy but gets converted later, we must overwrite that
+ * field. The conversion to the actual variable happens later after
+ * the initial fake time-step.
+ *
+ * @param p The #part to write to.
+ * @param u_init The new initial internal energy.
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_set_init_internal_energy(struct part *p, float u_init) {
+
+  p->u = u_init;
+}
+
+/**
+ * @brief Operations performed when a particle gets removed from the
+ * simulation volume.
+ *
+ * @param p The particle.
+ * @param xp The extended particle data.
+ * @param time The simulation time.
+ */
+__attribute__((always_inline)) INLINE static void hydro_remove_part(
+    const struct part *p, const struct xpart *xp, const double time) {
+
+  /* Print the particle info as csv to facilitate later analysis, e.g. with
+   * grep '## Removed' -A 1 --no-group-separator output.txt > removed.txt
+   */
+  printf(
+      "## Removed particle: "
+      "id, x, y, z, vx, vy, vz, m, u, P, rho, h, mat_id, time \n"
+      "%lld, %.7g, %.7g, %.7g, %.7g, %.7g, %.7g, "
+      "%.7g, %.7g, %.7g, %.7g, %.7g, %d, %.7g \n",
+      p->id, p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2], p->mass,
+      p->u, p->force.pressure, p->rho, p->h, p->mat_id, time);
+}
+
+#endif /* SWIFT_REMIX_HYDRO_H */
diff --git a/src/hydro/REMIX/hydro_debug.h b/src/hydro/REMIX/hydro_debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..5fbd6c6efd985a55c5c43f2df079ae8654f90347
--- /dev/null
+++ b/src/hydro/REMIX/hydro_debug.h
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Thomas Sandnes (thomas.d.sandnes@durham.ac.uk)
+ *               2024 Jacob Kegerreis (jacob.kegerreis@durham.ac.uk)
+ *               2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_REMIX_HYDRO_DEBUG_H
+#define SWIFT_REMIX_HYDRO_DEBUG_H
+
+/**
+ * @file REMIX/hydro_debug.h
+ * @brief Debugging routines for REMIX SPH.
+ */
+
+__attribute__((always_inline)) INLINE static void hydro_debug_particle(
+    const struct part* p, const struct xpart* xp) {
+  warning("[PID%lld] part:", p->id);
+  warning(
+      "[PID%lld] "
+      "x=[%.6g, %.6g, %.6g], v=[%.3g, %.3g, %.3g], "
+      "a=[%.3g, %.3g, %.3g], "
+      "m=%.3g, u=%.3g, du/dt=%.3g, P=%.3g, c_s=%.3g, "
+      "v_sig=%.3g, h=%.3g, dh/dt=%.3g, wcount=%.3g, rho=%.3g, "
+      "dh_drho=%.3g, rho_evol=%.3g, time_bin=%d, wakeup=%d, mat_id=%d",
+      p->id, p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2],
+      p->a_hydro[0], p->a_hydro[1], p->a_hydro[2], p->mass, p->u, p->u_dt,
+      hydro_get_comoving_pressure(p), p->force.soundspeed, p->force.v_sig, p->h,
+      p->force.h_dt, p->density.wcount, p->rho, p->density.rho_dh, p->rho_evol,
+      p->time_bin, p->limiter_data.wakeup, p->mat_id);
+  if (xp != NULL) {
+    warning("[PID%lld] xpart:", p->id);
+    warning(
+        "[PID%lld] "
+        "v_full=[%.3g, %.3g, %.3g]",
+        p->id, xp->v_full[0], xp->v_full[1], xp->v_full[2]);
+  }
+}
+
+#endif /* SWIFT_REMIX_HYDRO_DEBUG_H */
diff --git a/src/hydro/REMIX/hydro_iact.h b/src/hydro/REMIX/hydro_iact.h
new file mode 100644
index 0000000000000000000000000000000000000000..a1b27788ef59f410b15b6e7b99fc5bb42736156b
--- /dev/null
+++ b/src/hydro/REMIX/hydro_iact.h
@@ -0,0 +1,539 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Thomas Sandnes (thomas.d.sandnes@durham.ac.uk)
+ *               2024 Jacob Kegerreis (jacob.kegerreis@durham.ac.uk)
+ *               2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_REMIX_HYDRO_IACT_H
+#define SWIFT_REMIX_HYDRO_IACT_H
+
+/**
+ * @file REMIX/hydro_iact.h
+ * @brief REMIX implementation of SPH (Sandnes et al. 2025)
+ */
+
+#include "adiabatic_index.h"
+#include "const.h"
+#include "hydro_kernels.h"
+#include "hydro_parameters.h"
+#include "hydro_visc_difn.h"
+#include "math.h"
+#include "minmax.h"
+
+/**
+ * @brief Density interaction between two particles.
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_density(
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {
+
+  float wi, wj, wi_dx, wj_dx;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (pi->time_bin >= time_bin_inhibited)
+    error("Inhibited pi in interaction function!");
+  if (pj->time_bin >= time_bin_inhibited)
+    error("Inhibited pj in interaction function!");
+#endif
+
+  /* Get r */
+  const float r = sqrtf(r2);
+
+  /* Get the masses. */
+  const float mi = pi->mass;
+  const float mj = pj->mass;
+
+  /* Compute density of pi. */
+  const float hi_inv = 1.f / hi;
+  const float ui = r * hi_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  pi->rho += mj * wi;
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+
+  /* Compute density of pj. */
+  const float hj_inv = 1.f / hj;
+  const float uj = r * hj_inv;
+  kernel_deval(uj, &wj, &wj_dx);
+
+  pj->rho += mi * wj;
+  pj->density.rho_dh -= mi * (hydro_dimension * wj + uj * wj_dx);
+  pj->density.wcount += wj;
+  pj->density.wcount_dh -= (hydro_dimension * wj + uj * wj_dx);
+
+  hydro_runner_iact_density_extra_kernel(pi, pj, dx, wi, wj, wi_dx, wj_dx);
+}
+
+/**
+ * @brief Density interaction between two particles (non-symmetric).
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_density(
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, const struct part *restrict pj, const float a,
+    const float H) {
+
+  float wi, wi_dx;
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (pi->time_bin >= time_bin_inhibited)
+    error("Inhibited pi in interaction function!");
+  if (pj->time_bin >= time_bin_inhibited)
+    error("Inhibited pj in interaction function!");
+#endif
+
+  /* Get the masses. */
+  const float mj = pj->mass;
+
+  /* Get r. */
+  const float r = sqrtf(r2);
+
+  const float h_inv = 1.f / hi;
+  const float ui = r * h_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  pi->rho += mj * wi;
+  pi->density.rho_dh -= mj * (hydro_dimension * wi + ui * wi_dx);
+  pi->density.wcount += wi;
+  pi->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx);
+
+  hydro_runner_iact_nonsym_density_extra_kernel(pi, pj, dx, wi, wi_dx);
+}
+
+/**
+ * @brief Calculate the gradient interaction between particle i and particle j
+ *
+ * This method wraps around hydro_gradients_collect, which can be an empty
+ * method, in which case no gradients are used.
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_gradient(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
+
+  float wi, wj, wi_dx, wj_dx;
+
+  /* Get r. */
+  const float r = sqrtf(r2);
+
+  /* Compute kernel of pi. */
+  const float hi_inv = 1.f / hi;
+  const float ui = r * hi_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  /* Compute kernel of pj. */
+  const float hj_inv = 1.f / hj;
+  const float uj = r * hj_inv;
+  kernel_deval(uj, &wj, &wj_dx);
+
+  hydro_runner_iact_gradient_extra_kernel(pi, pj, dx, wi, wj, wi_dx, wj_dx);
+  hydro_runner_iact_gradient_extra_visc_difn(pi, pj, dx, wi, wj, wi_dx, wj_dx);
+}
+
+/**
+ * @brief Calculate the gradient interaction between particle i and particle j:
+ * non-symmetric version
+ *
+ * This method wraps around hydro_gradients_nonsym_collect, which can be an
+ * empty method, in which case no gradients are used.
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_gradient(
+    float r2, const float *dx, float hi, float hj, struct part *restrict pi,
+    struct part *restrict pj, float a, float H) {
+
+  float wi, wj, wi_dx, wj_dx;
+
+  /* Get r. */
+  const float r = sqrtf(r2);
+
+  /* Compute kernel of pi. */
+  const float h_inv = 1.f / hi;
+  const float ui = r * h_inv;
+  kernel_deval(ui, &wi, &wi_dx);
+
+  /* Compute kernel of pj. */
+  const float hj_inv = 1.f / hj;
+  const float uj = r * hj_inv;
+  kernel_deval(uj, &wj, &wj_dx);
+
+  hydro_runner_iact_nonsym_gradient_extra_kernel(pi, pj, dx, wi, wj, wi_dx,
+                                                 wj_dx);
+  hydro_runner_iact_nonsym_gradient_extra_visc_difn(pi, pj, dx, wi, wi_dx);
+}
+
+/**
+ * @brief Force interaction between two particles.
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_force(
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, struct part *restrict pj, const float a,
+    const float H) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (pi->time_bin >= time_bin_inhibited)
+    error("Inhibited pi in interaction function!");
+  if (pj->time_bin >= time_bin_inhibited)
+    error("Inhibited pj in interaction function!");
+#endif
+
+  /* Get r. */
+  const float r = sqrtf(r2);
+
+  /* Recover some data */
+  const float mi = pi->mass;
+  const float mj = pj->mass;
+  const float rhoi = pi->rho;
+  const float rhoj = pj->rho;
+  const float rhoi_inv = 1.f / rhoi;
+  const float rhoj_inv = 1.f / rhoj;
+  const float pressurei = pi->force.pressure;
+  const float pressurej = pj->force.pressure;
+
+  /* Get the kernel for hi. */
+  const float hi_inv = 1.0f / hi;
+  const float xi = r * hi_inv;
+  float wi, wi_dx;
+  kernel_deval(xi, &wi, &wi_dx);
+
+  /* Get the kernel for hj. */
+  const float hj_inv = 1.0f / hj;
+  const float xj = r * hj_inv;
+  float wj, wj_dx;
+  kernel_deval(xj, &wj, &wj_dx);
+
+  /* Linear-order reproducing kernel gradient term (Sandnes+2025 Eqn. 28) */
+  float Gj[3], Gi[3], G_mean[3];
+  hydro_set_Gi_Gj_forceloop(Gi, Gj, pi, pj, dx, wi, wj, wi_dx, wj_dx);
+
+  /* Antisymmetric kernel grad term for conservation of momentum and energy */
+  G_mean[0] = 0.5f * (Gi[0] - Gj[0]);
+  G_mean[1] = 0.5f * (Gi[1] - Gj[1]);
+  G_mean[2] = 0.5f * (Gi[2] - Gj[2]);
+
+  /* Viscous pressures (Sandnes+2025 Eqn. 41) */
+  float Qi, Qj;
+  float visc_signal_velocity, difn_signal_velocity;
+  hydro_set_Qi_Qj(&Qi, &Qj, &visc_signal_velocity, &difn_signal_velocity, pi,
+                  pj, dx);
+
+  /* Pressure terms to be used in evolution equations */
+  const float P_i_term = pressurei * rhoi_inv * rhoj_inv;
+  const float P_j_term = pressurej * rhoi_inv * rhoj_inv;
+  const float Q_i_term = Qi * rhoi_inv * rhoj_inv;
+  const float Q_j_term = Qj * rhoi_inv * rhoj_inv;
+
+  /* Use the force Luke! */
+  pi->a_hydro[0] -=
+      mj * (P_i_term + P_j_term + Q_i_term + Q_j_term) * G_mean[0];
+  pi->a_hydro[1] -=
+      mj * (P_i_term + P_j_term + Q_i_term + Q_j_term) * G_mean[1];
+  pi->a_hydro[2] -=
+      mj * (P_i_term + P_j_term + Q_i_term + Q_j_term) * G_mean[2];
+
+  pj->a_hydro[0] +=
+      mi * (P_i_term + P_j_term + Q_i_term + Q_j_term) * G_mean[0];
+  pj->a_hydro[1] +=
+      mi * (P_i_term + P_j_term + Q_i_term + Q_j_term) * G_mean[1];
+  pj->a_hydro[2] +=
+      mi * (P_i_term + P_j_term + Q_i_term + Q_j_term) * G_mean[2];
+
+  /* v_ij dot kernel gradient term */
+  const float dvdotG = (pi->v[0] - pj->v[0]) * G_mean[0] +
+                       (pi->v[1] - pj->v[1]) * G_mean[1] +
+                       (pi->v[2] - pj->v[2]) * G_mean[2];
+
+  /* Internal energy time derivative */
+  pi->u_dt += mj * (P_i_term + Q_i_term) * dvdotG;
+  pj->u_dt += mi * (P_j_term + Q_j_term) * dvdotG;
+
+  /* Density time derivative */
+  pi->drho_dt += mj * (rhoi * rhoj_inv) * dvdotG;
+  pj->drho_dt += mi * (rhoj * rhoi_inv) * dvdotG;
+
+  /* Get the time derivative for h. */
+  pi->force.h_dt -= mj * dvdotG * rhoj_inv;
+  pj->force.h_dt -= mi * dvdotG * rhoi_inv;
+
+  const float v_sig = visc_signal_velocity;
+
+  /* Update the signal velocity. */
+  pi->force.v_sig = max(pi->force.v_sig, v_sig);
+  pj->force.v_sig = max(pj->force.v_sig, v_sig);
+
+  if ((pi->is_h_max) || (pj->is_h_max)) {
+    /* Do not add diffusion or normalising terms if either particle has h=h_max
+     */
+    return;
+  }
+
+  /* Calculate some quantities */
+  const float mean_rho = 0.5f * (rhoi + rhoj);
+  const float mean_rho_inv = 1.f / mean_rho;
+  const float mean_balsara = 0.5f * (pi->force.balsara + pj->force.balsara);
+  const float mod_G = sqrtf(G_mean[0] * G_mean[0] + G_mean[1] * G_mean[1] +
+                            G_mean[2] * G_mean[2]);
+  const float v_sig_norm = sqrtf((pi->v[0] - pj->v[0]) * (pi->v[0] - pj->v[0]) +
+                                 (pi->v[1] - pj->v[1]) * (pi->v[1] - pj->v[1]) +
+                                 (pi->v[2] - pj->v[2]) * (pi->v[2] - pj->v[2]));
+
+  /* Add normalising term to density evolution (Sandnes+2025 Eqn. 51) */
+  const float alpha_norm = const_remix_norm_alpha;
+  float drho_dt_norm_and_difn_i = alpha_norm * mj * v_sig_norm *
+                                  pi->force.vac_switch *
+                                  (pi->m0 * rhoi - rhoi) * mod_G * mean_rho_inv;
+  float drho_dt_norm_and_difn_j = alpha_norm * mi * v_sig_norm *
+                                  pj->force.vac_switch *
+                                  (pj->m0 * rhoj - rhoj) * mod_G * mean_rho_inv;
+
+  /* Only include diffusion for same-material particle pair */
+  if (pi->mat_id == pj->mat_id) {
+    /* Diffusion parameters */
+    const float a_difn_rho = const_remix_difn_a_rho;
+    const float b_difn_rho = const_remix_difn_b_rho;
+    const float a_difn_u = const_remix_difn_a_u;
+    const float b_difn_u = const_remix_difn_b_u;
+
+    float utilde_i, utilde_j, rhotilde_i, rhotilde_j;
+    hydro_set_u_rho_difn(&utilde_i, &utilde_j, &rhotilde_i, &rhotilde_j, pi, pj,
+                         dx);
+    const float v_sig_difn = difn_signal_velocity;
+
+    /* Calculate artificial diffusion of internal energy (Sandnes+2025 Eqn. 42)
+     */
+    const float du_dt_difn_i = -(a_difn_u + b_difn_u * mean_balsara) * mj *
+                               v_sig_difn * (utilde_i - utilde_j) * mod_G *
+                               mean_rho_inv;
+    const float du_dt_difn_j = -(a_difn_u + b_difn_u * mean_balsara) * mi *
+                               v_sig_difn * (utilde_j - utilde_i) * mod_G *
+                               mean_rho_inv;
+
+    /* Add artificial diffusion to evolution of internal energy */
+    pi->u_dt += du_dt_difn_i;
+    pj->u_dt += du_dt_difn_j;
+
+    /* Calculate artificial diffusion of density (Sandnes+2025 Eqn. 43) */
+    drho_dt_norm_and_difn_i += -(a_difn_rho + b_difn_rho * mean_balsara) * mj *
+                               (rhoi * rhoj_inv) * v_sig_difn *
+                               (rhotilde_i - rhotilde_j) * mod_G * mean_rho_inv;
+    drho_dt_norm_and_difn_j += -(a_difn_rho + b_difn_rho * mean_balsara) * mi *
+                               (rhoj * rhoi_inv) * v_sig_difn *
+                               (rhotilde_j - rhotilde_i) * mod_G * mean_rho_inv;
+  }
+
+  /* Add normalising term and artificial diffusion to evolution of density */
+  pi->drho_dt += drho_dt_norm_and_difn_i;
+  pj->drho_dt += drho_dt_norm_and_difn_j;
+}
+
+/**
+ * @brief Force interaction between two particles (non-symmetric).
+ *
+ * @param r2 Comoving square distance between the two particles.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param hi Comoving smoothing-length of particle i.
+ * @param hj Comoving smoothing-length of particle j.
+ * @param pi First particle.
+ * @param pj Second particle (not updated).
+ * @param a Current scale factor.
+ * @param H Current Hubble parameter.
+ */
+__attribute__((always_inline)) INLINE static void runner_iact_nonsym_force(
+    const float r2, const float dx[3], const float hi, const float hj,
+    struct part *restrict pi, const struct part *restrict pj, const float a,
+    const float H) {
+
+#ifdef SWIFT_DEBUG_CHECKS
+  if (pi->time_bin >= time_bin_inhibited)
+    error("Inhibited pi in interaction function!");
+  if (pj->time_bin >= time_bin_inhibited)
+    error("Inhibited pj in interaction function!");
+#endif
+
+  /* Get r. */
+  const float r = sqrtf(r2);
+
+  /* Recover some data */
+  const float mj = pj->mass;
+  const float rhoi = pi->rho;
+  const float rhoj = pj->rho;
+  const float rhoi_inv = 1.f / rhoi;
+  const float rhoj_inv = 1.f / rhoj;
+  const float pressurei = pi->force.pressure;
+  const float pressurej = pj->force.pressure;
+
+  /* Get the kernel for hi. */
+  const float hi_inv = 1.0f / hi;
+  const float xi = r * hi_inv;
+  float wi, wi_dx;
+  kernel_deval(xi, &wi, &wi_dx);
+
+  /* Get the kernel for hj. */
+  const float hj_inv = 1.0f / hj;
+  const float xj = r * hj_inv;
+  float wj, wj_dx;
+  kernel_deval(xj, &wj, &wj_dx);
+
+  /* Linear-order reproducing kernel gradient term (Sandnes+2025 Eqn. 28) */
+  float Gj[3], Gi[3], G_mean[3];
+  hydro_set_Gi_Gj_forceloop(Gi, Gj, pi, pj, dx, wi, wj, wi_dx, wj_dx);
+
+  /* Antisymmetric kernel grad term for conservation of momentum and energy */
+  G_mean[0] = 0.5f * (Gi[0] - Gj[0]);
+  G_mean[1] = 0.5f * (Gi[1] - Gj[1]);
+  G_mean[2] = 0.5f * (Gi[2] - Gj[2]);
+
+  /* Viscous pressures (Sandnes+2025 Eqn. 41) */
+  float Qi, Qj;
+  float visc_signal_velocity, difn_signal_velocity;
+  hydro_set_Qi_Qj(&Qi, &Qj, &visc_signal_velocity, &difn_signal_velocity, pi,
+                  pj, dx);
+
+  /* Pressure terms to be used in evolution equations */
+  const float P_i_term = pressurei * rhoi_inv * rhoj_inv;
+  const float P_j_term = pressurej * rhoi_inv * rhoj_inv;
+  const float Q_i_term = Qi * rhoi_inv * rhoj_inv;
+  const float Q_j_term = Qj * rhoi_inv * rhoj_inv;
+
+  /* Use the force Luke! */
+  pi->a_hydro[0] -=
+      mj * (P_i_term + P_j_term + Q_i_term + Q_j_term) * G_mean[0];
+  pi->a_hydro[1] -=
+      mj * (P_i_term + P_j_term + Q_i_term + Q_j_term) * G_mean[1];
+  pi->a_hydro[2] -=
+      mj * (P_i_term + P_j_term + Q_i_term + Q_j_term) * G_mean[2];
+
+  /* v_ij dot kernel gradient term */
+  const float dvdotG = (pi->v[0] - pj->v[0]) * G_mean[0] +
+                       (pi->v[1] - pj->v[1]) * G_mean[1] +
+                       (pi->v[2] - pj->v[2]) * G_mean[2];
+
+  /* Internal energy time derivative */
+  pi->u_dt += mj * (P_i_term + Q_i_term) * dvdotG;
+
+  /* Density time derivative */
+  pi->drho_dt += mj * (rhoi * rhoj_inv) * dvdotG;
+
+  /* Get the time derivative for h. */
+  pi->force.h_dt -= mj * dvdotG * rhoj_inv;
+
+  const float v_sig = visc_signal_velocity;
+
+  /* Update the signal velocity. */
+  pi->force.v_sig = max(pi->force.v_sig, v_sig);
+
+  if ((pi->is_h_max) || (pj->is_h_max)) {
+    /* Do not add diffusion or normalising terms if either particle has h=h_max
+     */
+    return;
+  }
+
+  /* Calculate some quantities */
+  const float mean_rho = 0.5f * (rhoi + rhoj);
+  const float mean_rho_inv = 1.f / mean_rho;
+  const float mean_balsara = 0.5f * (pi->force.balsara + pj->force.balsara);
+  const float mod_G = sqrtf(G_mean[0] * G_mean[0] + G_mean[1] * G_mean[1] +
+                            G_mean[2] * G_mean[2]);
+
+  const float v_sig_norm = sqrtf((pi->v[0] - pj->v[0]) * (pi->v[0] - pj->v[0]) +
+                                 (pi->v[1] - pj->v[1]) * (pi->v[1] - pj->v[1]) +
+                                 (pi->v[2] - pj->v[2]) * (pi->v[2] - pj->v[2]));
+
+  /* Add normalising term to density evolution (Sandnes+2025 Eqn. 51) */
+  const float alpha_norm = const_remix_norm_alpha;
+  float drho_dt_norm_and_difn_i = alpha_norm * mj * v_sig_norm *
+                                  pi->force.vac_switch *
+                                  (pi->m0 * rhoi - rhoi) * mod_G * mean_rho_inv;
+
+  /* Only include diffusion for same-material particle pair */
+  if (pi->mat_id == pj->mat_id) {
+    /* Diffusion parameters */
+    const float a_difn_rho = const_remix_difn_a_rho;
+    const float b_difn_rho = const_remix_difn_b_rho;
+    const float a_difn_u = const_remix_difn_a_u;
+    const float b_difn_u = const_remix_difn_b_u;
+
+    float utilde_i, utilde_j, rhotilde_i, rhotilde_j;
+    hydro_set_u_rho_difn(&utilde_i, &utilde_j, &rhotilde_i, &rhotilde_j, pi, pj,
+                         dx);
+    const float v_sig_difn = difn_signal_velocity;
+
+    /* Calculate artificial diffusion of internal energy (Sandnes+2025 Eqn. 42)
+     */
+    const float du_dt_difn_i = -(a_difn_u + b_difn_u * mean_balsara) * mj *
+                               v_sig_difn * (utilde_i - utilde_j) * mod_G *
+                               mean_rho_inv;
+
+    /* Add artificial diffusion to evolution of internal energy */
+    pi->u_dt += du_dt_difn_i;
+
+    /* Calculate artificial diffusion of density (Sandnes+2025 Eqn. 43) */
+    drho_dt_norm_and_difn_i += -(a_difn_rho + b_difn_rho * mean_balsara) * mj *
+                               (rhoi * rhoj_inv) * v_sig_difn *
+                               (rhotilde_i - rhotilde_j) * mod_G * mean_rho_inv;
+  }
+
+  /* Add normalising term and artificial diffusion to evolution of density */
+  pi->drho_dt += drho_dt_norm_and_difn_i;
+}
+
+#endif /* SWIFT_REMIX_HYDRO_IACT_H */
diff --git a/src/hydro/REMIX/hydro_io.h b/src/hydro/REMIX/hydro_io.h
new file mode 100644
index 0000000000000000000000000000000000000000..1972ef029deb545ef18e486eb4eaf31c90476e4e
--- /dev/null
+++ b/src/hydro/REMIX/hydro_io.h
@@ -0,0 +1,251 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Thomas Sandnes (thomas.d.sandnes@durham.ac.uk)
+ *               2024 Jacob Kegerreis (jacob.kegerreis@durham.ac.uk)
+ *               2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_REMIX_HYDRO_IO_H
+#define SWIFT_REMIX_HYDRO_IO_H
+
+/**
+ * @file REMIX/hydro_io.h
+ * @brief REMIX implementation of SPH (Sandnes et al. 2025) i/o routines
+ */
+
+#include "adiabatic_index.h"
+#include "hydro.h"
+#include "hydro_parameters.h"
+#include "io_properties.h"
+#include "kernel_hydro.h"
+
+/**
+ * @brief Specifies which particle fields to read from a dataset
+ *
+ * @param parts The particle array.
+ * @param list The list of i/o properties to read.
+ * @param num_fields The number of i/o fields to read.
+ */
+INLINE static void hydro_read_particles(struct part* parts,
+                                        struct io_props* list,
+                                        int* num_fields) {
+
+#ifdef PLANETARY_FIXED_ENTROPY
+  *num_fields = 10;
+#else
+  *num_fields = 9;
+#endif
+
+  /* List what we want to read */
+  list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY,
+                                UNIT_CONV_LENGTH, parts, x);
+  list[1] = io_make_input_field("Velocities", FLOAT, 3, COMPULSORY,
+                                UNIT_CONV_SPEED, parts, v);
+  list[2] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, UNIT_CONV_MASS,
+                                parts, mass);
+  list[3] = io_make_input_field("SmoothingLength", FLOAT, 1, COMPULSORY,
+                                UNIT_CONV_LENGTH, parts, h);
+  list[4] = io_make_input_field("InternalEnergy", FLOAT, 1, COMPULSORY,
+                                UNIT_CONV_ENERGY_PER_UNIT_MASS, parts, u);
+  list[5] = io_make_input_field("ParticleIDs", ULONGLONG, 1, COMPULSORY,
+                                UNIT_CONV_NO_UNITS, parts, id);
+  list[6] = io_make_input_field("Accelerations", FLOAT, 3, OPTIONAL,
+                                UNIT_CONV_ACCELERATION, parts, a_hydro);
+  list[7] = io_make_input_field("Density", FLOAT, 1, COMPULSORY,
+                                UNIT_CONV_DENSITY, parts, rho);
+  list[8] = io_make_input_field("MaterialIDs", INT, 1, COMPULSORY,
+                                UNIT_CONV_NO_UNITS, parts, mat_id);
+#ifdef PLANETARY_FIXED_ENTROPY
+  list[9] = io_make_input_field("Entropies", FLOAT, 1, COMPULSORY,
+                                UNIT_CONV_PHYSICAL_ENTROPY_PER_UNIT_MASS, parts,
+                                s_fixed);
+#endif
+}
+
+INLINE static void convert_S(const struct engine* e, const struct part* p,
+                             const struct xpart* xp, float* ret) {
+
+  ret[0] = hydro_get_comoving_entropy(p, xp);
+}
+
+INLINE static void convert_P(const struct engine* e, const struct part* p,
+                             const struct xpart* xp, float* ret) {
+
+  ret[0] = hydro_get_comoving_pressure(p);
+}
+
+INLINE static void convert_part_pos(const struct engine* e,
+                                    const struct part* p,
+                                    const struct xpart* xp, double* ret) {
+  const struct space* s = e->s;
+  if (s->periodic) {
+    ret[0] = box_wrap(p->x[0], 0.0, s->dim[0]);
+    ret[1] = box_wrap(p->x[1], 0.0, s->dim[1]);
+    ret[2] = box_wrap(p->x[2], 0.0, s->dim[2]);
+  } else {
+    ret[0] = p->x[0];
+    ret[1] = p->x[1];
+    ret[2] = p->x[2];
+  }
+  if (e->snapshot_use_delta_from_edge) {
+    ret[0] = min(ret[0], s->dim[0] - e->snapshot_delta_from_edge);
+    ret[1] = min(ret[1], s->dim[1] - e->snapshot_delta_from_edge);
+    ret[2] = min(ret[2], s->dim[2] - e->snapshot_delta_from_edge);
+  }
+}
+
+INLINE static void convert_part_vel(const struct engine* e,
+                                    const struct part* p,
+                                    const struct xpart* xp, float* ret) {
+
+  const int with_cosmology = (e->policy & engine_policy_cosmology);
+  const struct cosmology* cosmo = e->cosmology;
+  const integertime_t ti_current = e->ti_current;
+  const double time_base = e->time_base;
+  const float dt_kick_grav_mesh = e->dt_kick_grav_mesh_for_io;
+
+  const integertime_t ti_beg = get_integer_time_begin(ti_current, p->time_bin);
+  const integertime_t ti_end = get_integer_time_end(ti_current, p->time_bin);
+
+  /* Get time-step since the last kick */
+  float dt_kick_grav, dt_kick_hydro;
+  if (with_cosmology) {
+    dt_kick_grav = cosmology_get_grav_kick_factor(cosmo, ti_beg, ti_current);
+    dt_kick_grav -=
+        cosmology_get_grav_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2);
+    dt_kick_hydro = cosmology_get_hydro_kick_factor(cosmo, ti_beg, ti_current);
+    dt_kick_hydro -=
+        cosmology_get_hydro_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2);
+  } else {
+    dt_kick_grav = (ti_current - ((ti_beg + ti_end) / 2)) * time_base;
+    dt_kick_hydro = (ti_current - ((ti_beg + ti_end) / 2)) * time_base;
+  }
+
+  /* Extrapolate the velocites to the current time (hydro term)*/
+  ret[0] = xp->v_full[0] + p->a_hydro[0] * dt_kick_hydro;
+  ret[1] = xp->v_full[1] + p->a_hydro[1] * dt_kick_hydro;
+  ret[2] = xp->v_full[2] + p->a_hydro[2] * dt_kick_hydro;
+
+  /* Add the gravity term */
+  if (p->gpart != NULL) {
+    ret[0] += p->gpart->a_grav[0] * dt_kick_grav;
+    ret[1] += p->gpart->a_grav[1] * dt_kick_grav;
+    ret[2] += p->gpart->a_grav[2] * dt_kick_grav;
+  }
+
+  /* And the mesh gravity term */
+  if (p->gpart != NULL) {
+    ret[0] += p->gpart->a_grav_mesh[0] * dt_kick_grav_mesh;
+    ret[1] += p->gpart->a_grav_mesh[1] * dt_kick_grav_mesh;
+    ret[2] += p->gpart->a_grav_mesh[2] * dt_kick_grav_mesh;
+  }
+
+  /* Conversion from internal units to peculiar velocities */
+  ret[0] *= cosmo->a_inv;
+  ret[1] *= cosmo->a_inv;
+  ret[2] *= cosmo->a_inv;
+}
+
+INLINE static void convert_part_potential(const struct engine* e,
+                                          const struct part* p,
+                                          const struct xpart* xp, float* ret) {
+
+  if (p->gpart != NULL)
+    ret[0] = gravity_get_comoving_potential(p->gpart);
+  else
+    ret[0] = 0.f;
+}
+
+/**
+ * @brief Specifies which particle fields to write to a dataset
+ *
+ * @param parts The particle array.
+ * @param xparts The extended particle array.
+ * @param list The list of i/o properties to write.
+ * @param num_fields The number of i/o fields to write.
+ */
+INLINE static void hydro_write_particles(const struct part* parts,
+                                         const struct xpart* xparts,
+                                         struct io_props* list,
+                                         int* num_fields) {
+
+  *num_fields = 11;
+
+  /* List what we want to write */
+  list[0] = io_make_output_field_convert_part(
+      "Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH, 1.f, parts, xparts,
+      convert_part_pos, "Positions of the particles");
+  list[1] = io_make_output_field_convert_part(
+      "Velocities", FLOAT, 3, UNIT_CONV_SPEED, 0.f, parts, xparts,
+      convert_part_vel, "Velocities of the particles");
+  list[2] = io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, 0.f, parts,
+                                 mass, "Masses of the particles");
+  list[3] = io_make_output_field(
+      "SmoothingLengths", FLOAT, 1, UNIT_CONV_LENGTH, 1.f, parts, h,
+      "Smoothing lengths (FWHM of the kernel) of the particles");
+  list[4] = io_make_output_field(
+      "InternalEnergies", FLOAT, 1, UNIT_CONV_ENERGY_PER_UNIT_MASS,
+      -3.f * hydro_gamma_minus_one, parts, u,
+      "Thermal energies per unit mass of the particles");
+  list[5] =
+      io_make_output_field("ParticleIDs", ULONGLONG, 1, UNIT_CONV_NO_UNITS, 0.f,
+                           parts, id, "Unique IDs of the particles");
+  list[6] = io_make_output_field("Densities", FLOAT, 1, UNIT_CONV_DENSITY, -3.f,
+                                 parts, rho, "Densities of the particles");
+  list[7] = io_make_output_field_convert_part(
+      "Entropies", FLOAT, 1, UNIT_CONV_ENTROPY_PER_UNIT_MASS, 0.f, parts,
+      xparts, convert_S, "Entropies per unit mass of the particles");
+  list[8] =
+      io_make_output_field("MaterialIDs", INT, 1, UNIT_CONV_NO_UNITS, 0.f,
+                           parts, mat_id, "Material IDs of the particles");
+  list[9] = io_make_output_field_convert_part(
+      "Pressures", FLOAT, 1, UNIT_CONV_PRESSURE, -3.f * hydro_gamma, parts,
+      xparts, convert_P, "Pressures of the particles");
+  list[10] = io_make_output_field_convert_part(
+      "Potentials", FLOAT, 1, UNIT_CONV_POTENTIAL, 0.f, parts, xparts,
+      convert_part_potential, "Gravitational potentials of the particles");
+}
+
+/**
+ * @brief Writes the current model of SPH to the file
+ * @param h_grpsph The HDF5 group in which to write
+ */
+INLINE static void hydro_write_flavour(hid_t h_grpsph) {
+
+  io_write_attribute_s(h_grpsph, "Density estimate", "Evolved in time");
+  io_write_attribute_s(h_grpsph, "EoM free functions", "Evolved densities");
+  io_write_attribute_s(h_grpsph, "Kernel gradients",
+                       "Linear-order reproducing kernels with grad-h terms");
+  io_write_attribute_s(h_grpsph, "Vacuum boundary treatment",
+                       "As presented in Sandnes et al. (2025)");
+  io_write_attribute_s(
+      h_grpsph, "Artificial viscosity",
+      "With linear reconstruction, van Leer slope limiter, Balsara switch");
+  io_write_attribute_s(
+      h_grpsph, "Artificial diffusion",
+      "With linear reconstruction, van Leer slope limiter, Balsara switch");
+  io_write_attribute_s(h_grpsph, "Normalising term",
+                       "As presented in Sandnes et al. (2025)");
+}
+
+/**
+ * @brief Are we writing entropy in the internal energy field ?
+ *
+ * @return 1 if entropy is in 'internal energy', 0 otherwise.
+ */
+INLINE static int writeEntropyFlag(void) { return 0; }
+
+#endif /* SWIFT_REMIX_HYDRO_IO_H */
diff --git a/src/hydro/REMIX/hydro_kernels.h b/src/hydro/REMIX/hydro_kernels.h
new file mode 100644
index 0000000000000000000000000000000000000000..8d4edcb69fc71f894fec89a6869921369a8283b0
--- /dev/null
+++ b/src/hydro/REMIX/hydro_kernels.h
@@ -0,0 +1,644 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Thomas Sandnes (thomas.d.sandnes@durham.ac.uk)
+ *               2024 Jacob Kegerreis (jacob.kegerreis@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_REMIX_HYDRO_KERNELS_H
+#define SWIFT_REMIX_HYDRO_KERNELS_H
+
+/**
+ * @file REMIX/hydro_kernels.h
+ * @brief Utilities for REMIX hydro kernels.
+ */
+
+#include "const.h"
+#include "hydro_parameters.h"
+#include "math.h"
+
+/**
+ * @brief Prepares extra kernel parameters for a particle for the density
+ * calculation.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void hydro_init_part_extra_kernel(
+    struct part *restrict p) {
+
+  p->m0 = 0.f;
+  p->grad_m0[0] = 0.f;
+  p->grad_m0[1] = 0.f;
+  p->grad_m0[2] = 0.f;
+}
+
+/**
+ * @brief Extra kernel density interaction between two particles
+ *
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param wi The value of the unmodified kernel function W(r, hi) * hi^d.
+ * @param wj The value of the unmodified kernel function W(r, hj) * hj^d.
+ * @param wi_dx The norm of the gradient of wi: dW(r, hi)/dr * hi^(d+1).
+ * @param wj_dx The norm of the gradient of wj: dW(r, hj)/dr * hj^(d+1).
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_runner_iact_density_extra_kernel(struct part *restrict pi,
+                                       struct part *restrict pj,
+                                       const float dx[3], const float wi,
+                                       const float wj, const float wi_dx,
+                                       const float wj_dx) {
+
+  /* Get r and 1/r. */
+  const float r = sqrtf(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]);
+  const float r_inv = r ? 1.0f / r : 0.0f;
+  const float volume_i = pi->mass / pi->rho_evol;
+  const float volume_j = pj->mass / pj->rho_evol;
+
+  /* Geometric moments and gradients that use an unmodified kernel (Sandnes+2025
+   * Eqn. 50 and its gradient). Used in the normalising term (Eqn. 51) and in
+   * gradient estimates (using Eqn. 30) that are used for the calculation of
+   * grad-h terms (Eqn. 31) and in the artificial viscosity (Eqn. 35) and
+   * diffusion (Eqns. 46 and 47) schemes */
+  pi->m0 += wi * volume_j;
+  pj->m0 += wj * volume_i;
+
+  pi->grad_m0[0] += dx[0] * wi_dx * r_inv * volume_j;
+  pi->grad_m0[1] += dx[1] * wi_dx * r_inv * volume_j;
+  pi->grad_m0[2] += dx[2] * wi_dx * r_inv * volume_j;
+
+  pj->grad_m0[0] += -dx[0] * wj_dx * r_inv * volume_i;
+  pj->grad_m0[1] += -dx[1] * wj_dx * r_inv * volume_i;
+  pj->grad_m0[2] += -dx[2] * wj_dx * r_inv * volume_i;
+}
+
+/**
+ * @brief Extra kernel density interaction between two particles (non-symmetric)
+ *
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param wi The value of the unmodified kernel function W(r, hi) * hi^d.
+ * @param wi_dx The norm of the gradient of wi: dW(r, hi)/dr * hi^(d+1).
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_runner_iact_nonsym_density_extra_kernel(struct part *restrict pi,
+                                              const struct part *restrict pj,
+                                              const float dx[3], const float wi,
+                                              const float wi_dx) {
+
+  /* Get r and 1/r. */
+  const float r = sqrtf(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]);
+  const float r_inv = r ? 1.0f / r : 0.0f;
+  const float volume_j = pj->mass / pj->rho_evol;
+
+  /* Geometric moments and gradients that use an unmodified kernel (Sandnes+2025
+   * Eqn. 50 and its gradient). Used in the normalising term (Eqn. 51) and in
+   * gradient estimates (using Eqn. 30) that are used for the calculation of
+   * grad-h terms (Eqn. 31) and in the artificial viscosity (Eqn. 35) and
+   * diffusion (Eqns. 46 and 47) schemes */
+  pi->m0 += wi * volume_j;
+
+  pi->grad_m0[0] += dx[0] * wi_dx * r_inv * volume_j;
+  pi->grad_m0[1] += dx[1] * wi_dx * r_inv * volume_j;
+  pi->grad_m0[2] += dx[2] * wi_dx * r_inv * volume_j;
+}
+
+/**
+ * @brief Finishes extra kernel parts of the density calculation.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_end_density_extra_kernel(struct part *restrict p) {
+
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                       /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv);       /* 1/h^d */
+  const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */
+
+  /* Geometric moments and gradients that use an unmodified kernel (Sandnes+2025
+   * Eqn. 50 and its gradient). Used in the normalising term (Eqn. 51) and in
+   * gradient estimates (using Eqn. 30) that are used for the calculation of
+   * grad-h terms (Eqn. 31) and in the artificial viscosity (Eqn. 35) and
+   * diffusion (Eqns. 46 and 47) schemes */
+  p->m0 += p->mass * kernel_root / p->rho_evol;
+  p->m0 *= h_inv_dim;
+  p->grad_m0[0] *= h_inv_dim_plus_one;
+  p->grad_m0[1] *= h_inv_dim_plus_one;
+  p->grad_m0[2] *= h_inv_dim_plus_one;
+}
+
+/**
+ * @brief Prepares extra kernel parameters for a particle for the gradient
+ * calculation.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_prepare_gradient_extra_kernel(struct part *restrict p) {
+
+  /* Initialise geometric moment matrices (ij-mean, for linear-order repr.
+   * kernel) */
+  zero_sym_matrix(&p->gradient.m2_bar);
+  zero_sym_matrix(&p->gradient.grad_m2_bar[0]);
+  zero_sym_matrix(&p->gradient.grad_m2_bar[1]);
+  zero_sym_matrix(&p->gradient.grad_m2_bar[2]);
+  zero_sym_matrix(&p->gradient.grad_m2_bar_gradhterm);
+
+  /* Geometric moments and gradients that us a kernel given by 0.5 * (W_{ij} +
+   * W_{ji}). These are used to construct the linear-order repr. kernel */
+  p->gradient.m0_bar = 0.f;
+  p->gradient.grad_m0_bar_gradhterm = 0.f;
+  memset(p->gradient.m1_bar, 0.f, 3 * sizeof(float));
+  memset(p->gradient.grad_m0_bar, 0.f, 3 * sizeof(float));
+  memset(p->gradient.grad_m1_bar_gradhterm, 0.f, 3 * sizeof(float));
+  memset(p->gradient.grad_m1_bar, 0.f, 3 * 3 * sizeof(float));
+}
+
+/**
+ * @brief Extra kernel gradient interaction between two particles
+ *
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param wi The value of the unmodified kernel function W(r, hi) * hi^d.
+ * @param wj The value of the unmodified kernel function W(r, hj) * hj^d.
+ * @param wi_dx The norm of the gradient of wi: dW(r, hi)/dr * hi^(d+1).
+ * @param wj_dx The norm of the gradient of wj: dW(r, hj)/dr * hj^(d+1).
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_runner_iact_gradient_extra_kernel(struct part *restrict pi,
+                                        struct part *restrict pj,
+                                        const float dx[3], const float wi,
+                                        const float wj, const float wi_dx,
+                                        const float wj_dx) {
+
+  /* Get r and 1/r. */
+  const float r = sqrtf(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]);
+  const float r_inv = r ? 1.0f / r : 0.0f;
+
+  const float hi = pi->h;
+  const float hi_inv = 1.0f / hi;                        /* 1/h */
+  const float hi_inv_dim = pow_dimension(hi_inv);        /* 1/h^d */
+  const float hi_inv_dim_plus_one = hi_inv_dim * hi_inv; /* 1/h^(d+1) */
+
+  const float hj = pj->h;
+  const float hj_inv = 1.0f / hj;                        /* 1/h */
+  const float hj_inv_dim = pow_dimension(hj_inv);        /* 1/h^d */
+  const float hj_inv_dim_plus_one = hj_inv_dim * hj_inv; /* 1/h^(d+1) */
+
+  /* Volume elements */
+  const float volume_i = pi->mass / pi->rho_evol;
+  const float volume_j = pj->mass / pj->rho_evol;
+
+  /* Mean ij kernels and gradients */
+  const float wi_term = 0.5f * (wi * hi_inv_dim + wj * hj_inv_dim);
+  const float wj_term = wi_term;
+  float wi_dx_term[3], wj_dx_term[3];
+  const float mean_dw_dr =
+      0.5f * (wi_dx * hi_inv_dim_plus_one + wj_dx * hj_inv_dim_plus_one);
+  wi_dx_term[0] = dx[0] * r_inv * mean_dw_dr;
+  wi_dx_term[1] = dx[1] * r_inv * mean_dw_dr;
+  wi_dx_term[2] = dx[2] * r_inv * mean_dw_dr;
+  wj_dx_term[0] = -wi_dx_term[0];
+  wj_dx_term[1] = -wi_dx_term[1];
+  wj_dx_term[2] = -wi_dx_term[2];
+
+  /* Grad-h term, dW/dh */
+  const float wi_dx_gradhterm = -0.5f *
+                                (hydro_dimension * wi + (r * hi_inv) * wi_dx) *
+                                hi_inv_dim_plus_one;
+  const float wj_dx_gradhterm = -0.5f *
+                                (hydro_dimension * wj + (r * hj_inv) * wj_dx) *
+                                hj_inv_dim_plus_one;
+
+  /* Geometric moments m_0, m_1, and m_2 (Sandnes+2025 Eqns. 24--26), their
+   * gradients (Sandnes+2025 Eqns. B.10--B.12, initially we only construct the
+   * first terms in Eqns. B.11 and B.12) and grad-h terms (from second term
+   * in Eqn. 29 when used in Eqns. B.10--B.12)*/
+  pi->gradient.m0_bar += wi_term * volume_j;
+  pj->gradient.m0_bar += wj_term * volume_i;
+
+  pi->gradient.grad_m0_bar_gradhterm += wi_dx_gradhterm * volume_j;
+  pj->gradient.grad_m0_bar_gradhterm += wj_dx_gradhterm * volume_i;
+  for (int i = 0; i < 3; i++) {
+    pi->gradient.m1_bar[i] += dx[i] * wi_term * volume_j;
+    pj->gradient.m1_bar[i] += -dx[i] * wj_term * volume_i;
+
+    pi->gradient.grad_m0_bar[i] += wi_dx_term[i] * volume_j;
+    pj->gradient.grad_m0_bar[i] += wj_dx_term[i] * volume_i;
+
+    pi->gradient.grad_m1_bar_gradhterm[i] += dx[i] * wi_dx_gradhterm * volume_j;
+    pj->gradient.grad_m1_bar_gradhterm[i] +=
+        -dx[i] * wj_dx_gradhterm * volume_i;
+
+    for (int j = 0; j < 3; j++) {
+      pi->gradient.grad_m1_bar[i][j] += dx[j] * wi_dx_term[i] * volume_j;
+      pj->gradient.grad_m1_bar[i][j] += -dx[j] * wj_dx_term[i] * volume_i;
+    }
+  }
+
+  for (int j = 0; j < 3; j++) {
+    for (int k = j; k < 3; k++) {
+      int i = (j == k) ? j : 2 + j + k;
+      pi->gradient.m2_bar.elements[i] += dx[j] * dx[k] * wi_term * volume_j;
+      pj->gradient.m2_bar.elements[i] += dx[j] * dx[k] * wj_term * volume_i;
+
+      pi->gradient.grad_m2_bar_gradhterm.elements[i] +=
+          dx[j] * dx[k] * wi_dx_gradhterm * volume_j;
+      pj->gradient.grad_m2_bar_gradhterm.elements[i] +=
+          dx[j] * dx[k] * wj_dx_gradhterm * volume_i;
+
+      for (int l = 0; l < 3; l++) {
+        pi->gradient.grad_m2_bar[l].elements[i] +=
+            dx[j] * dx[k] * wi_dx_term[l] * volume_j;
+        pj->gradient.grad_m2_bar[l].elements[i] +=
+            dx[j] * dx[k] * wj_dx_term[l] * volume_i;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Extra kernel gradient interaction between two particles
+ * (non-symmetric)
+ *
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param wi The value of the unmodified kernel function W(r, hi) * hi^d.
+ * @param wj The value of the unmodified kernel function W(r, hj) * hj^d.
+ * @param wi_dx The norm of the gradient of wi: dW(r, hi)/dr * hi^(d+1).
+ * @param wj_dx The norm of the gradient of wj: dW(r, hj)/dr * hj^(d+1).
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_runner_iact_nonsym_gradient_extra_kernel(
+    struct part *restrict pi, struct part *restrict pj, const float dx[3],
+    const float wi, const float wj, const float wi_dx, const float wj_dx) {
+
+  /* Get r and 1/r. */
+  const float r = sqrtf(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]);
+  const float r_inv = r ? 1.0f / r : 0.0f;
+
+  const float hi = pi->h;
+  const float hi_inv = 1.0f / hi;                        /* 1/h */
+  const float hi_inv_dim = pow_dimension(hi_inv);        /* 1/h^d */
+  const float hi_inv_dim_plus_one = hi_inv_dim * hi_inv; /* 1/h^(d+1) */
+
+  const float hj = pj->h;
+  const float hj_inv = 1.0f / hj;                        /* 1/h */
+  const float hj_inv_dim = pow_dimension(hj_inv);        /* 1/h^d */
+  const float hj_inv_dim_plus_one = hj_inv_dim * hj_inv; /* 1/h^(d+1) */
+
+  /* Volume elements */
+  const float volume_j = pj->mass / pj->rho_evol;
+
+  /* Mean ij kernel and gradients */
+  const float wi_term = 0.5f * (wi * hi_inv_dim + wj * hj_inv_dim);
+  float wi_dx_term[3];
+  const float mean_dw_dr =
+      0.5f * (wi_dx * hi_inv_dim_plus_one + wj_dx * hj_inv_dim_plus_one);
+  wi_dx_term[0] = dx[0] * r_inv * mean_dw_dr;
+  wi_dx_term[1] = dx[1] * r_inv * mean_dw_dr;
+  wi_dx_term[2] = dx[2] * r_inv * mean_dw_dr;
+
+  /* Grad-h term, dW/dh */
+  const float wi_dx_gradhterm = -0.5f *
+                                (hydro_dimension * wi + (r * hi_inv) * wi_dx) *
+                                hi_inv_dim_plus_one;
+
+  /* Geometric moments m_0, m_1, and m_2 (Sandnes+2025 Eqns. 24--26), their
+   * gradients (Sandnes+2025 Eqns. B.10--B.12, initially we only construct the
+   * first terms in Eqns. B.11 and B.12) and grad-h terms (from second term
+   * in Eqn. 29 when used in Eqns. B.10--B.12)*/
+  pi->gradient.m0_bar += wi_term * volume_j;
+
+  pi->gradient.grad_m0_bar_gradhterm += wi_dx_gradhterm * volume_j;
+  for (int i = 0; i < 3; i++) {
+    pi->gradient.m1_bar[i] += dx[i] * wi_term * volume_j;
+
+    pi->gradient.grad_m0_bar[i] += wi_dx_term[i] * volume_j;
+
+    pi->gradient.grad_m1_bar_gradhterm[i] += dx[i] * wi_dx_gradhterm * volume_j;
+
+    for (int j = 0; j < 3; j++) {
+      pi->gradient.grad_m1_bar[i][j] += dx[j] * wi_dx_term[i] * volume_j;
+    }
+  }
+
+  for (int j = 0; j < 3; j++) {
+    for (int k = j; k < 3; k++) {
+      int i = (j == k) ? j : 2 + j + k;
+      pi->gradient.m2_bar.elements[i] += dx[j] * dx[k] * wi_term * volume_j;
+
+      pi->gradient.grad_m2_bar_gradhterm.elements[i] +=
+          dx[j] * dx[k] * wi_dx_gradhterm * volume_j;
+
+      for (int l = 0; l < 3; l++) {
+        pi->gradient.grad_m2_bar[l].elements[i] +=
+            dx[j] * dx[k] * wi_dx_term[l] * volume_j;
+      }
+    }
+  }
+}
+
+/**
+ * @brief Finishes extra kernel parts of the gradient calculation.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_end_gradient_extra_kernel(struct part *restrict p) {
+
+  const float h = p->h;
+  const float h_inv = 1.0f / h;                       /* 1/h */
+  const float h_inv_dim = pow_dimension(h_inv);       /* 1/h^d */
+  const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */
+
+  /* Volume elements */
+  const float volume = p->mass / p->rho_evol;
+
+  /* Self contribution to geometric moments and gradients */
+  p->gradient.m0_bar += volume * kernel_root * h_inv_dim;
+  p->gradient.grad_m0_bar_gradhterm -=
+      0.5f * volume * hydro_dimension * kernel_root * h_inv_dim_plus_one;
+
+  /* Multiply dh/dr (Sandnes+2025 Eqn. 31) into grad-h terms (See second term
+   * in Sandnes+2025 Eqn. 29) */
+  for (int i = 0; i < 3; i++) {
+    p->gradient.grad_m0_bar[i] +=
+        p->gradient.grad_m0_bar_gradhterm * p->dh_norm_kernel[i];
+    for (int j = 0; j < 3; j++) {
+      p->gradient.grad_m1_bar[i][j] +=
+          p->gradient.grad_m1_bar_gradhterm[j] * p->dh_norm_kernel[i];
+    }
+    for (int k = 0; k < 6; k++) {
+      p->gradient.grad_m2_bar[i].elements[k] +=
+          p->gradient.grad_m2_bar_gradhterm.elements[k] * p->dh_norm_kernel[i];
+    }
+  }
+}
+
+/**
+ * @brief Prepare a particle for the force calculation.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_prepare_force_extra_kernel(struct part *restrict p) {
+
+  if (p->is_h_max) {
+    /* Use standard kernal gradients if h=h_max */
+    /* Linear-order reproducing kernel parameters are set to "defaults" */
+    p->force.A = 1.f;
+    p->force.vac_switch = 1.f;
+    memset(p->force.B, 0.f, 3 * sizeof(float));
+    memset(p->force.grad_A, 0.f, 3 * sizeof(float));
+    memset(p->force.grad_B, 0.f, 3 * 3 * sizeof(float));
+
+    return;
+  }
+
+  /* Add second terms in Sandnes+2025 Eqns. B.11 and B.12 to complete the
+   * geometric moment gradients */
+  for (int i = 0; i < 3; i++) {
+    p->gradient.grad_m1_bar[i][i] += p->gradient.m0_bar;
+  }
+
+  p->gradient.grad_m2_bar[0].xx += 2.f * p->gradient.m1_bar[0];
+  p->gradient.grad_m2_bar[0].xy += p->gradient.m1_bar[1];
+  p->gradient.grad_m2_bar[0].xz += p->gradient.m1_bar[2];
+
+  p->gradient.grad_m2_bar[1].yy += 2.f * p->gradient.m1_bar[1];
+  p->gradient.grad_m2_bar[1].xy += p->gradient.m1_bar[0];
+  p->gradient.grad_m2_bar[1].yz += p->gradient.m1_bar[2];
+
+  p->gradient.grad_m2_bar[2].zz += 2.f * p->gradient.m1_bar[2];
+  p->gradient.grad_m2_bar[2].xz += p->gradient.m1_bar[0];
+  p->gradient.grad_m2_bar[2].yz += p->gradient.m1_bar[1];
+
+  /* Inverse of symmetric geometric moment m_2 (bar) */
+  struct sym_matrix m2_bar_inv;
+  /* Make m2_bar dimensionless for calculation of inverse */
+  struct sym_matrix m2_bar_over_h2;
+  for (int i = 0; i < 6; i++) {
+    m2_bar_over_h2.elements[i] = p->gradient.m2_bar.elements[i] / (p->h * p->h);
+  }
+  sym_matrix_invert(&m2_bar_inv, &m2_bar_over_h2);
+  for (int i = 0; i < 6; i++) {
+    m2_bar_inv.elements[i] /= (p->h * p->h);
+  }
+
+  /* Components for constructing linear-order kernel's A and B (Sandnes+2025
+   * Eqns. 22 and 23), and gradients (Eqns. B.8 and B.9) that are calculated
+   * with sym_matrix functions from combinations of geometric moments and
+   * their gradients */
+  float m2_bar_inv_mult_m1_bar[3];
+  float m2_bar_inv_mult_grad_m1_bar[3][3];
+  struct sym_matrix m2_bar_inv_mult_grad_m2_bar_mult_m2_bar_inv[3];
+  float ABA_mult_m1_bar[3][3];
+
+  sym_matrix_multiply_by_vector(m2_bar_inv_mult_m1_bar, &m2_bar_inv,
+                                p->gradient.m1_bar);
+  for (int i = 0; i < 3; i++) {
+    sym_matrix_multiply_by_vector(m2_bar_inv_mult_grad_m1_bar[i], &m2_bar_inv,
+                                  p->gradient.grad_m1_bar[i]);
+    sym_matrix_multiplication_ABA(
+        &m2_bar_inv_mult_grad_m2_bar_mult_m2_bar_inv[i], &m2_bar_inv,
+        &p->gradient.grad_m2_bar[i]);
+    sym_matrix_multiply_by_vector(
+        ABA_mult_m1_bar[i], &m2_bar_inv_mult_grad_m2_bar_mult_m2_bar_inv[i],
+        p->gradient.m1_bar);
+  }
+
+  /* Linear-order reproducing kernel's A and B components (Sandnes+2025
+   * Eqns. 22 and 23) and gradients (Eqns. B.8 and B.9) */
+  float A, B[3], grad_A[3], grad_B[3][3];
+
+  /* Calculate A (Sandnes+2025 Eqn. 22) */
+  A = p->gradient.m0_bar;
+  for (int i = 0; i < 3; i++) {
+    A -= m2_bar_inv_mult_m1_bar[i] * p->gradient.m1_bar[i];
+  }
+  A = 1.f / A;
+
+  /* Calculate B (Sandnes+2025 Eqn. 23) */
+  for (int i = 0; i < 3; i++) {
+    B[i] = -m2_bar_inv_mult_m1_bar[i];
+  }
+
+  /* Calculate grad A (Sandnes+2025 Eqn. B.8) */
+  for (int i = 0; i < 3; i++) {
+    grad_A[i] = p->gradient.grad_m0_bar[i];
+
+    for (int j = 0; j < 3; j++) {
+      grad_A[i] +=
+          -2 * m2_bar_inv_mult_m1_bar[j] * p->gradient.grad_m1_bar[i][j] +
+          ABA_mult_m1_bar[i][j] * p->gradient.m1_bar[j];
+    }
+
+    grad_A[i] *= -A * A;
+  }
+
+  /* Calculate grad B (Sandnes+2025 Eqn. B.9) */
+  for (int i = 0; i < 3; i++) {
+    for (int j = 0; j < 3; j++) {
+      grad_B[j][i] = -m2_bar_inv_mult_grad_m1_bar[j][i] + ABA_mult_m1_bar[j][i];
+    }
+  }
+
+  /* Store final values */
+  p->force.A = A;
+  memcpy(p->force.B, B, 3 * sizeof(float));
+  memcpy(p->force.grad_A, grad_A, 3 * sizeof(float));
+  memcpy(p->force.grad_B, grad_B, 3 * 3 * sizeof(float));
+
+  /* Vacuum-boundary proximity switch (Sandnes+2025 Eqn. 33) */
+  p->force.vac_switch = 1.f;
+  const float hB = p->h * sqrtf(B[0] * B[0] + B[1] * B[1] + B[2] * B[2]);
+  const float offset = 0.8f;
+
+  if (hB > offset) {
+    const float sigma = 0.2f;
+    p->force.vac_switch =
+        expf(-(hB - offset) * (hB - offset) / (2.f * sigma * sigma));
+  }
+}
+
+/**
+ * @brief Set gradient terms for linear-order reproducing kernel.
+ *
+ * Note `G` here corresponds to `d/dr tilde{mathcal{W}}` in Sandnes et
+ * al.(2025). These are used in the REMIX equations of motion (Sandnes+2025
+ * Eqns. 14--16).
+ *
+ * @param Gi (return) Gradient of linear-order reproducing kernel for first
+ * particle.
+ * @param Gj (return) Gradient of linear-order reproducing kernel for second
+ * particle.
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param wi The value of the unmodified kernel function W(r, hi) * hi^d.
+ * @param wj The value of the unmodified kernel function W(r, hj) * hj^d.
+ * @param wi_dx The norm of the gradient of wi: dW(r, hi)/dr * hi^(d+1).
+ * @param wj_dx The norm of the gradient of wj: dW(r, hj)/dr * hj^(d+1).
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_Gi_Gj_forceloop(
+    float Gi[3], float Gj[3], const struct part *restrict pi,
+    const struct part *restrict pj, const float dx[3], const float wi,
+    const float wj, const float wi_dx, const float wj_dx) {
+
+  /* Get r and 1/r. */
+  const float r = sqrtf(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]);
+  const float r_inv = r ? 1.0f / r : 0.0f;
+
+  const float hi = pi->h;
+  const float hi_inv = 1.0f / hi;                        /* 1/h */
+  const float hi_inv_dim = pow_dimension(hi_inv);        /* 1/h^d */
+  const float hi_inv_dim_plus_one = hi_inv_dim * hi_inv; /* 1/h^(d+1) */
+
+  const float hj = pj->h;
+  const float hj_inv = 1.0f / hj;                        /* 1/h */
+  const float hj_inv_dim = pow_dimension(hj_inv);        /* 1/h^d */
+  const float hj_inv_dim_plus_one = hj_inv_dim * hj_inv; /* 1/h^(d+1) */
+
+  const float wi_dr = hi_inv_dim_plus_one * wi_dx;
+  const float wj_dr = hj_inv_dim_plus_one * wj_dx;
+
+  if ((pi->is_h_max) || (pj->is_h_max)) {
+    /* If one or both particles have h=h_max, revert to standard kernel grads,
+     * without grad-h terms */
+    Gi[0] = wi_dr * dx[0] * r_inv;
+    Gi[1] = wi_dr * dx[1] * r_inv;
+    Gi[2] = wi_dr * dx[2] * r_inv;
+
+    Gj[0] = -wj_dr * dx[0] * r_inv;
+    Gj[1] = -wj_dr * dx[1] * r_inv;
+    Gj[2] = -wj_dr * dx[2] * r_inv;
+
+    return;
+  }
+
+  /* Mean ij kernels and gradients with grad-h terms */
+  const float wi_term = 0.5f * (wi * hi_inv_dim + wj * hj_inv_dim);
+  const float wj_term = wi_term;
+  float wi_dx_term[3], wj_dx_term[3];
+
+  /* Get linear-order reproducing kernel's A and B components (Sandnes+2025
+   * Eqns. 22 and 23) and gradients (Eqns. B.8 and B.9) */
+  const float Ai = pi->force.A;
+  const float Aj = pj->force.A;
+  float grad_Ai[3], grad_Aj[3], Bi[3], Bj[3], grad_Bi[3][3], grad_Bj[3][3];
+  memcpy(grad_Ai, pi->force.grad_A, 3 * sizeof(float));
+  memcpy(grad_Aj, pj->force.grad_A, 3 * sizeof(float));
+  memcpy(Bi, pi->force.B, 3 * sizeof(float));
+  memcpy(Bj, pj->force.B, 3 * sizeof(float));
+  memcpy(grad_Bi, pi->force.grad_B, 3 * 3 * sizeof(float));
+  memcpy(grad_Bj, pj->force.grad_B, 3 * 3 * sizeof(float));
+
+  for (int i = 0; i < 3; i++) {
+
+    /* Assemble Sandnes+2025 Eqn. 29 */
+    const float mean_dw_dr =
+        0.5f * (wi_dx * hi_inv_dim_plus_one + wj_dx * hj_inv_dim_plus_one);
+    wi_dx_term[i] = dx[i] * r_inv * mean_dw_dr;
+    wj_dx_term[i] = -wi_dx_term[i];
+
+    wi_dx_term[i] += -0.5f * (hydro_dimension * wi + (r * hi_inv) * wi_dx) *
+                     hi_inv_dim_plus_one * pi->dh_norm_kernel[i];
+    wj_dx_term[i] += -0.5f * (hydro_dimension * wj + (r * hj_inv) * wj_dx) *
+                     hj_inv_dim_plus_one * pj->dh_norm_kernel[i];
+
+    /* Assemble Sandnes+2025 Eqn. 28 */
+    Gi[i] = Ai * wi_dx_term[i] + grad_Ai[i] * wi_term + Ai * Bi[i] * wi_term;
+    Gj[i] = Aj * wj_dx_term[i] + grad_Aj[i] * wj_term + Aj * Bj[i] * wj_term;
+
+    for (int j = 0; j < 3; j++) {
+      Gi[i] += Ai * Bi[j] * dx[j] * wi_dx_term[i] +
+               grad_Ai[i] * Bi[j] * dx[j] * wi_term +
+               Ai * grad_Bi[i][j] * dx[j] * wi_term;
+
+      Gj[i] += -(Aj * Bj[j] * dx[j] * wj_dx_term[i] +
+                 grad_Aj[i] * Bj[j] * dx[j] * wj_term +
+                 Aj * grad_Bj[i][j] * dx[j] * wj_term);
+    }
+  }
+
+  /* Standard-kernel gradients, to be used for vacuum boundary switch (For
+   * second term in Sandnes+2025 Eqn. 32)*/
+  float wi_dx_term_vac[3], wj_dx_term_vac[3];
+  for (int i = 0; i < 3; i++) {
+    wi_dx_term_vac[i] = dx[i] * r_inv * wi_dx * hi_inv_dim_plus_one;
+    wj_dx_term_vac[i] = -dx[i] * r_inv * wj_dx * hj_inv_dim_plus_one;
+
+    wi_dx_term_vac[i] += -(hydro_dimension * wi + (r * hi_inv) * wi_dx) *
+                         hi_inv_dim_plus_one * pi->dh_norm_kernel[i];
+    wj_dx_term_vac[i] += -(hydro_dimension * wj + (r * hj_inv) * wj_dx) *
+                         hj_inv_dim_plus_one * pj->dh_norm_kernel[i];
+  }
+
+  /* Gradients, including vacuum boundary switch (Sandnes+2025 Eqn. 32) */
+  for (int i = 0; i < 3; i++) {
+    Gi[i] = pi->force.vac_switch * Gi[i] +
+            (1.f - pi->force.vac_switch) * wi_dx_term_vac[i];
+    Gj[i] = pj->force.vac_switch * Gj[i] +
+            (1.f - pj->force.vac_switch) * wj_dx_term_vac[i];
+  }
+}
+
+#endif /* SWIFT_REMIX_HYDRO_KERNELS_H */
diff --git a/src/hydro/REMIX/hydro_parameters.h b/src/hydro/REMIX/hydro_parameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..e33568a31b92c2543e9d60860b9d565746ba720d
--- /dev/null
+++ b/src/hydro/REMIX/hydro_parameters.h
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Thomas Sandnes (thomas.d.sandnes@durham.ac.uk)
+ *               2024 Jacob Kegerreis (jacob.kegerreis@durham.ac.uk)
+ *               2019 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_REMIX_HYDRO_PARAMETERS_H
+#define SWIFT_REMIX_HYDRO_PARAMETERS_H
+
+/* Configuration file */
+#include "config.h"
+
+/* Global headers */
+#if defined(HAVE_HDF5)
+#include <hdf5.h>
+#endif
+
+/* Local headers */
+#include "common_io.h"
+#include "error.h"
+#include "inline.h"
+
+/**
+ * @file REMIX/hydro_parameters.h
+ * @brief REMIX implementation of SPH (Sandnes et al. 2025) (default parameters)
+ *
+ * This file defines a number of things that are used in
+ * hydro_properties.c as defaults for run-time parameters
+ * as well as a number of compile-time parameters.
+ */
+
+/* Viscosity paramaters -- Defaults; can be changed at run-time */
+
+/*! Default REMIX artificial viscosity parameters */
+#define const_remix_visc_alpha 1.5f
+#define const_remix_visc_beta 3.f
+#define const_remix_visc_epsilon 0.1f
+#define const_remix_visc_a 2.0f / 3.0f
+#define const_remix_visc_b 1.0f / 3.0f
+#define const_remix_difn_a_u 0.05f
+#define const_remix_difn_b_u 0.95f
+#define const_remix_difn_a_rho 0.05f
+#define const_remix_difn_b_rho 0.95f
+#define const_remix_norm_alpha 1.0f
+#define const_remix_slope_limiter_exp_denom 0.04f
+
+/* The viscosity that the particles are reset to after being hit by a
+ * feedback event. This should be set to the same value as the
+ * const_viscosity_alpha in fixed schemes, and likely
+ * to const_viscosity_alpha_max in variable schemes. */
+#define hydro_props_default_viscosity_alpha_feedback_reset 1.5f
+
+/* Structs that store the relevant variables */
+
+/*! Artificial viscosity parameters */
+struct viscosity_global_data {};
+
+/*! Artificial diffusion parameters */
+struct diffusion_global_data {};
+
+/* Functions for reading from parameter file */
+
+/* Forward declartions */
+struct swift_params;
+struct phys_const;
+struct unit_system;
+
+/* Viscosity */
+
+/**
+ * @brief Initialises the viscosity parameters in the struct from
+ *        the parameter file, or sets them to defaults.
+ *
+ * @param params: the pointer to the swift_params file
+ * @param unit_system: pointer to the unit system
+ * @param phys_const: pointer to the physical constants system
+ * @param viscosity: pointer to the viscosity_global_data struct to be filled.
+ **/
+static INLINE void viscosity_init(struct swift_params* params,
+                                  const struct unit_system* us,
+                                  const struct phys_const* phys_const,
+                                  struct viscosity_global_data* viscosity) {}
+
+/**
+ * @brief Initialises a viscosity struct to sensible numbers for mocking
+ *        purposes.
+ *
+ * @param viscosity: pointer to the viscosity_global_data struct to be filled.
+ **/
+static INLINE void viscosity_init_no_hydro(
+    struct viscosity_global_data* viscosity) {}
+
+/**
+ * @brief Prints out the viscosity parameters at the start of a run.
+ *
+ * @param viscosity: pointer to the viscosity_global_data struct found in
+ *                   hydro_properties
+ **/
+static INLINE void viscosity_print(
+    const struct viscosity_global_data* viscosity) {}
+
+#if defined(HAVE_HDF5)
+/**
+ * @brief Prints the viscosity information to the snapshot when writing.
+ *
+ * @param h_grpsph: the SPH group in the ICs to write attributes to.
+ * @param viscosity: pointer to the viscosity_global_data struct.
+ **/
+static INLINE void viscosity_print_snapshot(
+    hid_t h_grpsph, const struct viscosity_global_data* viscosity) {
+
+  io_write_attribute_f(h_grpsph, "Viscosity alpha", const_remix_visc_alpha);
+  io_write_attribute_f(h_grpsph, "Viscosity beta", const_remix_visc_beta);
+  io_write_attribute_f(h_grpsph, "Viscosity epsilon", const_remix_visc_epsilon);
+  io_write_attribute_f(h_grpsph, "Viscosity a_visc", const_remix_visc_a);
+  io_write_attribute_f(h_grpsph, "Viscosity b_visc", const_remix_visc_b);
+}
+#endif
+
+/* Diffusion */
+
+/**
+ * @brief Initialises the diffusion parameters in the struct from
+ *        the parameter file, or sets them to defaults.
+ *
+ * @param params: the pointer to the swift_params file
+ * @param unit_system: pointer to the unit system
+ * @param phys_const: pointer to the physical constants system
+ * @param diffusion_global_data: pointer to the diffusion struct to be filled.
+ **/
+static INLINE void diffusion_init(struct swift_params* params,
+                                  const struct unit_system* us,
+                                  const struct phys_const* phys_const,
+                                  struct diffusion_global_data* diffusion) {}
+
+/**
+ * @brief Initialises a diffusion struct to sensible numbers for mocking
+ *        purposes.
+ *
+ * @param diffusion: pointer to the diffusion_global_data struct to be filled.
+ **/
+static INLINE void diffusion_init_no_hydro(
+    struct diffusion_global_data* diffusion) {}
+
+/**
+ * @brief Prints out the diffusion parameters at the start of a run.
+ *
+ * @param diffusion: pointer to the diffusion_global_data struct found in
+ *                   hydro_properties
+ **/
+static INLINE void diffusion_print(
+    const struct diffusion_global_data* diffusion) {}
+
+#ifdef HAVE_HDF5
+/**
+ * @brief Prints the diffusion information to the snapshot when writing.
+ *
+ * @param h_grpsph: the SPH group in the ICs to write attributes to.
+ * @param diffusion: pointer to the diffusion_global_data struct.
+ **/
+static INLINE void diffusion_print_snapshot(
+    hid_t h_grpsph, const struct diffusion_global_data* diffusion) {
+  io_write_attribute_f(h_grpsph, "Diffusion a_difn_u", const_remix_difn_a_u);
+  io_write_attribute_f(h_grpsph, "Diffusion b_difn_u", const_remix_difn_b_u);
+  io_write_attribute_f(h_grpsph, "Diffusion a_difn_rho",
+                       const_remix_difn_a_rho);
+  io_write_attribute_f(h_grpsph, "Diffusion b_difn_rho",
+                       const_remix_difn_b_rho);
+}
+#endif
+
+#endif /* SWIFT_REMIX_HYDRO_PARAMETERS_H */
diff --git a/src/hydro/REMIX/hydro_part.h b/src/hydro/REMIX/hydro_part.h
new file mode 100644
index 0000000000000000000000000000000000000000..e0a642ad0dbd6bdab7670c60bb85f1f143d53d10
--- /dev/null
+++ b/src/hydro/REMIX/hydro_part.h
@@ -0,0 +1,313 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Thomas Sandnes (thomas.d.sandnes@durham.ac.uk)
+ *               2024 Jacob Kegerreis (jacob.kegerreis@durham.ac.uk)
+ *               2016 Matthieu Schaller (matthieu.schaller@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_REMIX_HYDRO_PART_H
+#define SWIFT_REMIX_HYDRO_PART_H
+
+/**
+ * @file REMIX/hydro_part.h
+ * @brief REMIX implementation of SPH (Sandnes et al. 2025)
+ */
+
+#include "black_holes_struct.h"
+#include "chemistry_struct.h"
+#include "cooling_struct.h"
+#include "equation_of_state.h" /* For enum material_id */
+#include "feedback_struct.h"
+#include "mhd_struct.h"
+#include "particle_splitting_struct.h"
+#include "rt_struct.h"
+#include "sink_struct.h"
+#include "star_formation_struct.h"
+#include "symmetric_matrix.h"
+#include "timestep_limiter_struct.h"
+#include "tracers_struct.h"
+
+/**
+ * @brief Particle fields not needed during the SPH loops over neighbours.
+ *
+ * This structure contains the particle fields that are not used in the
+ * density or force loops. Quantities should be used in the kick, drift and
+ * potentially ghost tasks only.
+ */
+struct xpart {
+
+  /*! Offset between current position and position at last tree rebuild. */
+  float x_diff[3];
+
+  /*! Offset between the current position and position at the last sort. */
+  float x_diff_sort[3];
+
+  /*! Velocity at the last full step. */
+  float v_full[3];
+
+  /*! Gravitational acceleration at the end of the last step */
+  float a_grav[3];
+
+  /*! Internal energy at the last full step. */
+  float u_full;
+
+  /*! Evolved density at the last full step. */
+  float rho_evol_full;
+
+  /*! Additional data used to record particle splits */
+  struct particle_splitting_data split_data;
+
+  /*! Additional data used to record cooling information */
+  struct cooling_xpart_data cooling_data;
+
+  /*! Additional data used by the tracers */
+  struct tracers_xpart_data tracers_data;
+
+  /*! Additional data used by the star formation */
+  struct star_formation_xpart_data sf_data;
+
+  /*! Additional data used by the feedback */
+  struct feedback_part_data feedback_data;
+
+  /*! Additional data used by the MHD scheme */
+  struct mhd_xpart_data mhd_data;
+
+} SWIFT_STRUCT_ALIGN;
+
+/**
+ * @brief Particle fields for the SPH particles
+ *
+ * The density and force substructures are used to contain variables only used
+ * within the density and force loops over neighbours. All more permanent
+ * variables should be declared in the main part of the part structure,
+ */
+struct part {
+
+  /*! Particle unique ID. */
+  long long id;
+
+  /*! Pointer to corresponding gravity part. */
+  struct gpart* gpart;
+
+  /*! Particle position. */
+  double x[3];
+
+  /*! Particle predicted velocity. */
+  float v[3];
+
+  /*! Particle acceleration. */
+  float a_hydro[3];
+
+  /*! Particle mass. */
+  float mass;
+
+  /*! Particle smoothing length. */
+  float h;
+
+  /*! Particle internal energy. */
+  float u;
+
+  /*! Time derivative of the internal energy. */
+  float u_dt;
+
+  /*! Particle density (standard SPH estimate). */
+  float rho;
+
+  /*! Particle evolved density (primary density for REMIX equations). */
+  float rho_evol;
+
+  /*! Time derivative of the evolved density. */
+  float drho_dt;
+
+  /*! Gradient of velocity, calculated using normalised kernel. */
+  float dv_norm_kernel[3][3];
+
+  /*! Gradient of internal energy, calculated using normalised kernel. */
+  float du_norm_kernel[3];
+
+  /*! Gradient of density, calculated using normalised kernel. */
+  float drho_norm_kernel[3];
+
+  /*! Gradient of smoothing length, calculated using normalised kernel. */
+  float dh_norm_kernel[3];
+
+  /*! Geometric moment m_0. */
+  float m0;
+
+  /*! Gradient of geometric moment m_0. */
+  float grad_m0[3];
+
+  /* Store density/force specific stuff. */
+  union {
+
+    /**
+     * @brief Structure for the variables only used in the density loop over
+     * neighbours.
+     *
+     * Quantities in this sub-structure should only be accessed in the density
+     * loop over neighbours and the ghost task.
+     */
+    struct {
+
+      /*! Neighbour number count. */
+      float wcount;
+
+      /*! Derivative of the neighbour number with respect to h. */
+      float wcount_dh;
+
+      /*! Derivative of density with respect to h */
+      float rho_dh;
+
+    } density;
+
+    /**
+     * @brief Structure for the variables only used in the gradient loop over
+     * neighbours.
+     *
+     * Quantities should only be accessed in the gradient loop over neighbours
+     * and the extra ghost task.
+     */
+    struct {
+
+      /*! Symmetrised kernel geometric moment m0. */
+      float m0_bar;
+
+      /*! Gradient of symmetrised kernel geometric moment m0. */
+      float grad_m0_bar[3];
+
+      /*! Contributing term for grad-h component of grad_m0_bar. */
+      float grad_m0_bar_gradhterm;
+
+      /*! Symmetrised kernel geometric moment m1. */
+      float m1_bar[3];
+
+      /*! Gradient of symmetrised kernel geometric moment m1. */
+      float grad_m1_bar[3][3];
+
+      /*! Contributing term for grad-h component of grad_m1_bar. */
+      float grad_m1_bar_gradhterm[3];
+
+      /*! Symmetrised kernel geometric moment m2. */
+      struct sym_matrix m2_bar;
+
+      /*! Gradient of symmetrised kernel geometric moment m2 (x, y, z
+       * components). */
+      struct sym_matrix grad_m2_bar[3];
+
+      /*! Contributing term for grad-h component of grad_m2_bar. */
+      struct sym_matrix grad_m2_bar_gradhterm;
+
+    } gradient;
+
+    /**
+     * @brief Structure for the variables only used in the force loop over
+     * neighbours.
+     *
+     * Quantities in this sub-structure should only be accessed in the force
+     * loop over neighbours and the ghost, drift and kick tasks.
+     */
+    struct {
+
+      /*! Particle pressure. */
+      float pressure;
+
+      /*! Particle soundspeed. */
+      float soundspeed;
+
+      /*! Particle signal velocity */
+      float v_sig;
+
+      /*! Time derivative of smoothing length  */
+      float h_dt;
+
+      /*! Balsara switch */
+      float balsara;
+
+      /*! Linear-order reproducing kernel coefficient */
+      float A;
+
+      /*! Linear-order reproducing kernel coefficient */
+      float B[3];
+
+      /*! Gradient of linear-order reproducing kernel coefficient */
+      float grad_A[3];
+
+      /*! Gradient of linear-order reproducing kernel coefficient */
+      float grad_B[3][3];
+
+      /*! Variable switch to identify proximity to vacuum boundaries. */
+      float vac_switch;
+
+      /*! Constant needed for slope limiter: 1 / eta_neighbours. */
+      float eta_crit;
+
+    } force;
+  };
+
+  /*! Additional data used by the MHD scheme */
+  struct mhd_part_data mhd_data;
+
+  /*! Chemistry information */
+  struct chemistry_part_data chemistry_data;
+
+  /*! Cooling information */
+  struct cooling_part_data cooling_data;
+
+  /*! Black holes information (e.g. swallowing ID) */
+  struct black_holes_part_data black_holes_data;
+
+  /*! Sink information (e.g. swallowing ID) */
+  struct sink_part_data sink_data;
+
+  /*! Material identifier flag */
+  enum eos_planetary_material_id mat_id;
+
+  /*! Additional Radiative Transfer Data */
+  struct rt_part_data rt_data;
+
+  /*! RT sub-cycling time stepping data */
+  struct rt_timestepping_data rt_time_data;
+
+  /*! Time-step length */
+  timebin_t time_bin;
+
+  /*! Tree-depth at which size / 2 <= h * gamma < size */
+  char depth_h;
+
+  /* Whether or not the particle has h=h_max ('1' or '0') */
+  char is_h_max;
+
+  /*! Time-step limiter information */
+  struct timestep_limiter_data limiter_data;
+
+#ifdef SWIFT_DEBUG_CHECKS
+
+  /* Time of the last drift */
+  integertime_t ti_drift;
+
+  /* Time of the last kick */
+  integertime_t ti_kick;
+
+#endif
+
+#ifdef PLANETARY_FIXED_ENTROPY
+  /* Fixed specific entropy */
+  float s_fixed;
+#endif
+
+} SWIFT_STRUCT_ALIGN;
+
+#endif /* SWIFT_REMIX_HYDRO_PART_H */
diff --git a/src/hydro/REMIX/hydro_visc_difn.h b/src/hydro/REMIX/hydro_visc_difn.h
new file mode 100644
index 0000000000000000000000000000000000000000..8f387c3c4d768e4c30e0f440179a97f02c190efe
--- /dev/null
+++ b/src/hydro/REMIX/hydro_visc_difn.h
@@ -0,0 +1,452 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2024 Thomas Sandnes (thomas.d.sandnes@durham.ac.uk)
+ *               2024 Jacob Kegerreis (jacob.kegerreis@durham.ac.uk)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_REMIX_HYDRO_VISC_DIFN_H
+#define SWIFT_REMIX_HYDRO_VISC_DIFN_H
+
+/**
+ * @file REMIX/hydro_visc_difn.h
+ * @brief Utilities for REMIX artificial viscosity and diffusion calculations.
+ */
+
+#include "const.h"
+#include "hydro_parameters.h"
+#include "math.h"
+
+/**
+ * @brief Prepares extra artificial viscosity and artificial diffusion
+ * parameters for a particle for the gradient calculation.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_prepare_gradient_extra_visc_difn(struct part *restrict p) {
+
+  memset(p->du_norm_kernel, 0.f, 3 * sizeof(float));
+  memset(p->drho_norm_kernel, 0.f, 3 * sizeof(float));
+  memset(p->dh_norm_kernel, 0.f, 3 * sizeof(float));
+  memset(p->dv_norm_kernel, 0.f, 3 * 3 * sizeof(float));
+}
+
+/**
+ * @brief Extra artificial viscosity and artificial diffusion gradient
+ * interaction between two particles
+ *
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param wi The value of the unmodified kernel function W(r, hi) * hi^d.
+ * @param wj The value of the unmodified kernel function W(r, hj) * hj^d.
+ * @param wi_dx The norm of the gradient of wi: dW(r, hi)/dr * hi^(d+1).
+ * @param wj_dx The norm of the gradient of wj: dW(r, hj)/dr * hj^(d+1).
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_runner_iact_gradient_extra_visc_difn(struct part *restrict pi,
+                                           struct part *restrict pj,
+                                           const float dx[3], const float wi,
+                                           const float wj, const float wi_dx,
+                                           const float wj_dx) {
+
+  /* Get r and 1/r. */
+  const float r = sqrtf(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]);
+  const float r_inv = r ? 1.0f / r : 0.0f;
+
+  const float hi = pi->h;
+  const float hi_inv = 1.0f / hi;                        /* 1/h */
+  const float hi_inv_dim = pow_dimension(hi_inv);        /* 1/h^d */
+  const float hi_inv_dim_plus_one = hi_inv_dim * hi_inv; /* 1/h^(d+1) */
+
+  const float hj = pj->h;
+  const float hj_inv = 1.0f / hj;                        /* 1/h */
+  const float hj_inv_dim = pow_dimension(hj_inv);        /* 1/h^d */
+  const float hj_inv_dim_plus_one = hj_inv_dim * hj_inv; /* 1/h^(d+1) */
+
+  const float volume_i = pi->mass / pi->rho_evol;
+  const float volume_j = pj->mass / pj->rho_evol;
+
+  /* Gradients of normalised kernel (Sandnes+2025 Eqn. 30) */
+  float wi_dx_term[3], wj_dx_term[3];
+  for (int i = 0; i < 3; i++) {
+    wi_dx_term[i] = dx[i] * r_inv * wi_dx * hi_inv_dim_plus_one;
+    wj_dx_term[i] = -dx[i] * r_inv * wj_dx * hj_inv_dim_plus_one;
+
+    wi_dx_term[i] *= (1.f / pi->m0);
+    wj_dx_term[i] *= (1.f / pj->m0);
+
+    wi_dx_term[i] += -wi * hi_inv_dim * pi->grad_m0[i] / (pi->m0 * pi->m0);
+    wj_dx_term[i] += -wj * hj_inv_dim * pj->grad_m0[i] / (pj->m0 * pj->m0);
+  }
+
+  /* Gradient estimates of h (Sandnes+2025 Eqn. 31), v (Eqn. 35), u (Eqn. 46),
+   * rho (Eqn. 47) using normalised kernel */
+  for (int i = 0; i < 3; i++) {
+    pi->dh_norm_kernel[i] += (pj->h - pi->h) * wi_dx_term[i] * volume_j;
+    pj->dh_norm_kernel[i] += (pi->h - pj->h) * wj_dx_term[i] * volume_i;
+
+    /* Contributions only from same-material particles for u and rho diffusion
+     */
+    if (pi->mat_id == pj->mat_id) {
+      pi->du_norm_kernel[i] += (pj->u - pi->u) * wi_dx_term[i] * volume_j;
+      pj->du_norm_kernel[i] += (pi->u - pj->u) * wj_dx_term[i] * volume_i;
+
+      pi->drho_norm_kernel[i] +=
+          (pj->rho_evol - pi->rho_evol) * wi_dx_term[i] * volume_j;
+      pj->drho_norm_kernel[i] +=
+          (pi->rho_evol - pj->rho_evol) * wj_dx_term[i] * volume_i;
+    }
+
+    for (int j = 0; j < 3; j++) {
+      pi->dv_norm_kernel[i][j] +=
+          (pj->v[j] - pi->v[j]) * wi_dx_term[i] * volume_j;
+      pj->dv_norm_kernel[i][j] +=
+          (pi->v[j] - pj->v[j]) * wj_dx_term[i] * volume_i;
+    }
+  }
+}
+
+/**
+ * @brief Extra artificial viscosity and artificial diffusion gradient
+ * interaction between two particles (non-symmetric)
+ *
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ * @param wi The value of the unmodified kernel function W(r, hi) * hi^d.
+ * @param wi_dx The norm of the gradient of wi: dW(r, hi)/dr * hi^(d+1).
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_runner_iact_nonsym_gradient_extra_visc_difn(
+    struct part *restrict pi, const struct part *restrict pj, const float dx[3],
+    const float wi, const float wi_dx) {
+
+  /* Get r and 1/r. */
+  const float r = sqrtf(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]);
+  const float r_inv = r ? 1.0f / r : 0.0f;
+
+  const float hi = pi->h;
+  const float hi_inv = 1.0f / hi;                        /* 1/h */
+  const float hi_inv_dim = pow_dimension(hi_inv);        /* 1/h^d */
+  const float hi_inv_dim_plus_one = hi_inv_dim * hi_inv; /* 1/h^(d+1) */
+
+  const float volume_j = pj->mass / pj->rho_evol;
+
+  /* Gradients of normalised kernel (Sandnes+2025 Eqn. 30) */
+  float wi_dx_term[3];
+  for (int i = 0; i < 3; i++) {
+    wi_dx_term[i] = dx[i] * r_inv * wi_dx * hi_inv_dim_plus_one;
+    wi_dx_term[i] *= (1.f / pi->m0);
+    wi_dx_term[i] += -wi * hi_inv_dim * pi->grad_m0[i] / (pi->m0 * pi->m0);
+  }
+
+  /* Gradient estimates of h (Sandnes+2025 Eqn. 31), v (Eqn. 35), u (Eqn. 46),
+   * rho (Eqn. 47) using normalised kernel */
+  for (int i = 0; i < 3; i++) {
+    pi->dh_norm_kernel[i] += (pj->h - pi->h) * wi_dx_term[i] * volume_j;
+
+    /* Contributions only from same-material particles for u and rho diffusion
+     */
+    if (pi->mat_id == pj->mat_id) {
+      pi->du_norm_kernel[i] += (pj->u - pi->u) * wi_dx_term[i] * volume_j;
+      pi->drho_norm_kernel[i] +=
+          (pj->rho_evol - pi->rho_evol) * wi_dx_term[i] * volume_j;
+    }
+
+    for (int j = 0; j < 3; j++) {
+      pi->dv_norm_kernel[i][j] +=
+          (pj->v[j] - pi->v[j]) * wi_dx_term[i] * volume_j;
+    }
+  }
+}
+
+/**
+ * @brief Finishes extra artificial viscosity and artificial diffusion parts of
+ * the gradient calculation.
+ *
+ * @param p The particle to act upon
+ */
+__attribute__((always_inline)) INLINE static void
+hydro_end_gradient_extra_visc_difn(struct part *restrict p) {}
+
+/**
+ * @brief Returns particle viscous pressures
+ *
+ * @param Qi (return) Viscous pressure for first particle.
+ * @param Qj (return) Viscous pressure for second particle.
+ * @param visc_signal_velocity (return) Signal velocity of artificial viscosity.
+ * @param difn_signal_velocity (return) Signal velocity of artificial diffusion.
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_Qi_Qj(
+    float *Qi, float *Qj, float *visc_signal_velocity,
+    float *difn_signal_velocity, const struct part *restrict pi,
+    const struct part *restrict pj, const float dx[3]) {
+
+  const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+  const float r = sqrtf(r2);
+
+  const float hi_inv = 1.0f / pi->h;
+  const float hj_inv = 1.0f / pj->h;
+
+  /* Reconstructed velocities at the halfway point between particles
+   * (Sandnes+2025 Eqn. 36) */
+  float vtilde_i[3], vtilde_j[3];
+
+  /* Viscosity parameters. These are set in hydro_parameters.h  */
+  const float alpha = const_remix_visc_alpha;
+  const float beta = const_remix_visc_beta;
+  const float epsilon = const_remix_visc_epsilon;
+  const float a_visc = const_remix_visc_a;
+  const float b_visc = const_remix_visc_b;
+  const float eta_crit = 0.5f * (pi->force.eta_crit + pj->force.eta_crit);
+  const float slope_limiter_exp_denom = const_remix_slope_limiter_exp_denom;
+
+  if ((pi->is_h_max) || (pj->is_h_max)) {
+    /* Don't reconstruct velocity if either particle has h=h_max */
+    vtilde_i[0] = 0.f;
+    vtilde_i[1] = 0.f;
+    vtilde_i[2] = 0.f;
+
+    vtilde_j[0] = 0.f;
+    vtilde_j[1] = 0.f;
+    vtilde_j[2] = 0.f;
+
+  } else {
+    /* A numerators and denominators (Sandnes+2025 Eqn. 38) */
+    float A_i_v = 0.f;
+    float A_j_v = 0.f;
+
+    /* 1/2 * (r_j - r_i) * dv/dr in second term of Sandnes+2025 Eqn. 38 */
+    float v_reconst_i[3] = {0};
+    float v_reconst_j[3] = {0};
+
+    for (int i = 0; i < 3; i++) {
+      for (int j = 0; j < 3; j++) {
+        /* Get the A numerators and denominators (Sandnes+2025 Eqn. 38).
+         * dv_norm_kernel is from Eqn. 35 */
+        A_i_v += pi->dv_norm_kernel[i][j] * dx[i] * dx[j];
+        A_j_v += pj->dv_norm_kernel[i][j] * dx[i] * dx[j];
+
+        v_reconst_i[j] -= 0.5 * pi->dv_norm_kernel[i][j] * dx[i];
+        v_reconst_j[j] += 0.5 * pj->dv_norm_kernel[i][j] * dx[i];
+      }
+    }
+
+    /* Slope limiter (Sandnes+2025 Eqn. 37) special cases */
+    float phi_i_v, phi_j_v;
+    if ((A_i_v == 0.f) && (A_j_v == 0.f)) {
+      phi_i_v = 1.f;
+      phi_j_v = 1.f;
+
+    } else if ((A_i_v == 0.f && A_j_v != 0.f) ||
+               (A_j_v == 0.f && A_i_v != 0.f) || (A_i_v == -A_j_v)) {
+      phi_i_v = 0.f;
+      phi_j_v = 0.f;
+    } else {
+      /* Slope limiter (Sandnes+2025 Eqn. 37) */
+      phi_i_v = min(1.f, 4.f * A_i_v / A_j_v / (1.f + A_i_v / A_j_v) /
+                             (1.f + A_i_v / A_j_v));
+      phi_i_v = max(0.f, phi_i_v);
+
+      phi_j_v = min(1.f, 4.f * A_j_v / A_i_v / (1.f + A_j_v / A_i_v) /
+                             (1.f + A_j_v / A_i_v));
+      phi_j_v = max(0.f, phi_j_v);
+    }
+
+    /* exp in slope limiter (middle case in Sandnes+2025 Eqn. 37) */
+    const float eta_ab = min(r * hi_inv, r * hj_inv);
+    if (eta_ab < eta_crit) {
+      phi_i_v *= expf(-(eta_ab - eta_crit) * (eta_ab - eta_crit) /
+                      slope_limiter_exp_denom);
+      phi_j_v *= expf(-(eta_ab - eta_crit) * (eta_ab - eta_crit) /
+                      slope_limiter_exp_denom);
+    }
+
+    for (int i = 0; i < 3; i++) {
+      /* Assemble the reconstructed velocity (Sandnes+2025 Eqn. 36) */
+      vtilde_i[i] =
+          pi->v[i] + (1.f - pi->force.balsara) * phi_i_v * v_reconst_i[i];
+      vtilde_j[i] =
+          pj->v[i] + (1.f - pj->force.balsara) * phi_j_v * v_reconst_j[i];
+    }
+  }
+
+  /* Assemble Sandnes+2025 Eqn. 40 */
+  const float mu_i =
+      min(0.f, ((vtilde_i[0] - vtilde_j[0]) * dx[0] +
+                (vtilde_i[1] - vtilde_j[1]) * dx[1] +
+                (vtilde_i[2] - vtilde_j[2]) * dx[2]) *
+                   hi_inv / (r2 * hi_inv * hi_inv + epsilon * epsilon));
+  const float mu_j =
+      min(0.f, ((vtilde_i[0] - vtilde_j[0]) * dx[0] +
+                (vtilde_i[1] - vtilde_j[1]) * dx[1] +
+                (vtilde_i[2] - vtilde_j[2]) * dx[2]) *
+                   hj_inv / (r2 * hj_inv * hj_inv + epsilon * epsilon));
+
+  const float ci = pi->force.soundspeed;
+  const float cj = pj->force.soundspeed;
+
+  /* Finally assemble viscous pressure terms (Sandnes+2025 41) */
+  *Qi = (a_visc + b_visc * pi->force.balsara) * 0.5f * pi->rho *
+        (-alpha * ci * mu_i + beta * mu_i * mu_i);
+  *Qj = (a_visc + b_visc * pj->force.balsara) * 0.5f * pj->rho *
+        (-alpha * cj * mu_j + beta * mu_j * mu_j);
+
+  /* Account for alpha being outside brackets in timestep code */
+  const float viscosity_parameter_factor = (alpha == 0.f) ? 0.f : beta / alpha;
+  *visc_signal_velocity =
+      ci + cj - 2.f * viscosity_parameter_factor * min(mu_i, mu_j);
+
+  /* Signal velocity used for the artificial diffusion (Sandnes+2025 Eqns. 42
+   * and 43) */
+  *difn_signal_velocity =
+      sqrtf((vtilde_i[0] - vtilde_j[0]) * (vtilde_i[0] - vtilde_j[0]) +
+            (vtilde_i[1] - vtilde_j[1]) * (vtilde_i[1] - vtilde_j[1]) +
+            (vtilde_i[2] - vtilde_j[2]) * (vtilde_i[2] - vtilde_j[2]));
+}
+
+/**
+ * @brief Returns midpoint reconstructions of internal energies and densities
+ *
+ * @param utilde_i (return) u reconstructed to midpoint from first particle.
+ * @param utilde_j (return) u reconstructed to midpoint from second particle.
+ * @param rhotilde_i (return) rho reconstructed to midpoint from first particle.
+ * @param rhotilde_j (return) rho reconstructed to midpoint from second
+ * particle.
+ * @param pi First particle.
+ * @param pj Second particle.
+ * @param dx Comoving vector separating both particles (pi - pj).
+ */
+__attribute__((always_inline)) INLINE static void hydro_set_u_rho_difn(
+    float *utilde_i, float *utilde_j, float *rhotilde_i, float *rhotilde_j,
+    const struct part *restrict pi, const struct part *restrict pj,
+    const float dx[3]) {
+
+  const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
+  const float r = sqrtf(r2);
+
+  const float hi_inv = 1.0f / pi->h;
+  const float hj_inv = 1.0f / pj->h;
+
+  const float eta_crit = 0.5f * (pi->force.eta_crit + pj->force.eta_crit);
+  const float slope_limiter_exp_denom = const_remix_slope_limiter_exp_denom;
+
+  if ((pi->is_h_max) || (pj->is_h_max)) {
+    /* Don't reconstruct internal energy of density if either particle has
+     * h=h_max */
+    *utilde_i = 0.f;
+    *utilde_j = 0.f;
+    *rhotilde_i = 0.f;
+    *rhotilde_j = 0.f;
+
+    return;
+  }
+
+  /* A numerators and denominators (Sandnes+2025 Eqns. 48 and 49) */
+  float A_i_u = 0.f;
+  float A_j_u = 0.f;
+  float A_i_rho = 0.f;
+  float A_j_rho = 0.f;
+
+  /* 1/2 * (r_j - r_i) * du/dr in second term of Sandnes+2025 Eqn. 44 */
+  float u_reconst_i = 0.f;
+  float u_reconst_j = 0.f;
+
+  /* 1/2 * (r_j - r_i) * drho/dr in second term of Sandnes+2025 Eqn. 45 */
+  float rho_reconst_i = 0.f;
+  float rho_reconst_j = 0.f;
+
+  for (int i = 0; i < 3; i++) {
+    /* Get the A numerators and denominators (Sandnes+2025 Eqns. 48 and 49).
+     * du_norm_kernel is from Eqn. 46 and drho_norm_kernel is from Eqn. 47 */
+    A_i_u += pi->du_norm_kernel[i] * dx[i];
+    A_j_u += pj->du_norm_kernel[i] * dx[i];
+    A_i_rho += pi->drho_norm_kernel[i] * dx[i];
+    A_j_rho += pj->drho_norm_kernel[i] * dx[i];
+
+    u_reconst_i -= 0.5 * pi->du_norm_kernel[i] * dx[i];
+    u_reconst_j += 0.5 * pj->du_norm_kernel[i] * dx[i];
+    rho_reconst_i -= 0.5 * pi->drho_norm_kernel[i] * dx[i];
+    rho_reconst_j += 0.5 * pj->drho_norm_kernel[i] * dx[i];
+  }
+
+  float phi_i_u, phi_j_u, phi_i_rho, phi_j_rho;
+  /* Slope limiter (Sandnes+2025 Eqn. 37) special cases */
+  if ((A_i_u == 0.f) && (A_j_u == 0.f)) {
+    phi_i_u = 1.f;
+    phi_j_u = 1.f;
+
+  } else if ((A_i_u == 0.f && A_j_u != 0.f) || (A_j_u == 0.f && A_i_u != 0.f) ||
+             (A_i_u == -A_j_u)) {
+    phi_i_u = 0.f;
+    phi_j_u = 0.f;
+  } else {
+    /* Slope limiter (Sandnes+2025 Eqn. 37) */
+    phi_i_u = min(1.f, 4.f * A_i_u / A_j_u / (1.f + A_i_u / A_j_u) /
+                           (1.f + A_i_u / A_j_u));
+    phi_i_u = max(0.f, phi_i_u);
+
+    phi_j_u = min(1.f, 4.f * A_j_u / A_i_u / (1.f + A_j_u / A_i_u) /
+                           (1.f + A_j_u / A_i_u));
+    phi_j_u = max(0.f, phi_j_u);
+  }
+
+  /* Slope limiter (Sandnes+2025 Eqn. 37) special cases */
+  if ((A_i_rho == 0.f) && (A_j_rho == 0.f)) {
+    phi_i_rho = 1.f;
+    phi_j_rho = 1.f;
+
+  } else if ((A_i_rho == 0.f && A_j_rho != 0.f) ||
+             (A_j_rho == 0.f && A_i_rho != 0.f) || (A_i_rho == -A_j_rho)) {
+    phi_i_rho = 0.f;
+    phi_j_rho = 0.f;
+  } else {
+    /* Slope limiter (Sandnes+2025 Eqn. 37) */
+    phi_i_rho = min(1.f, 4.f * A_i_rho / A_j_rho / (1.f + A_i_rho / A_j_rho) /
+                             (1.f + A_i_rho / A_j_rho));
+    phi_i_rho = max(0.f, phi_i_rho);
+
+    phi_j_rho = min(1.f, 4.f * A_j_rho / A_i_rho / (1.f + A_j_rho / A_i_rho) /
+                             (1.f + A_j_rho / A_i_rho));
+    phi_j_rho = max(0.f, phi_j_rho);
+  }
+
+  /* exp in slope limiter (middle case in Sandnes+2025 Eqn. 37) */
+  const float eta_ab = min(r * hi_inv, r * hj_inv);
+  if (eta_ab < eta_crit) {
+    phi_i_u *= expf(-(eta_ab - eta_crit) * (eta_ab - eta_crit) /
+                    slope_limiter_exp_denom);
+    phi_j_u *= expf(-(eta_ab - eta_crit) * (eta_ab - eta_crit) /
+                    slope_limiter_exp_denom);
+    phi_i_rho *= expf(-(eta_ab - eta_crit) * (eta_ab - eta_crit) /
+                      slope_limiter_exp_denom);
+    phi_j_rho *= expf(-(eta_ab - eta_crit) * (eta_ab - eta_crit) /
+                      slope_limiter_exp_denom);
+  }
+
+  /* Assemble the reconstructed internal energy (Sandnes+2025 Eqn. 44) and
+   * density (Sandnes+2025 Eqn. 45) */
+  *utilde_i = pi->u + phi_i_u * u_reconst_i;
+  *utilde_j = pj->u + phi_j_u * u_reconst_j;
+  *rhotilde_i = pi->rho_evol + phi_i_rho * rho_reconst_i;
+  *rhotilde_j = pj->rho_evol + phi_j_rho * rho_reconst_j;
+}
+
+#endif /* SWIFT_REMIX_HYDRO_VISC_DIFN_H */
diff --git a/src/hydro_csds.h b/src/hydro_csds.h
index 96cb374423a438c5c42ea1f6a284c6d06f6815b6..a0593ca7b327fef1f98d8c3cf84590733fa67a40 100644
--- a/src/hydro_csds.h
+++ b/src/hydro_csds.h
@@ -47,6 +47,8 @@
 #error TODO
 #elif defined(PLANETARY_SPH)
 #error TODO
+#elif defined(REMIX_SPH)
+#error TODO
 #elif defined(SPHENIX_SPH)
 #include "./hydro/SPHENIX/hydro_csds.h"
 #elif defined(GASOLINE_SPH)
diff --git a/src/hydro_io.h b/src/hydro_io.h
index ed78acf691d2ac172cd2b8fa81456a2514fdcb98..5a64a284cc4b7121bb50dec4f92df717cdef3ad5 100644
--- a/src/hydro_io.h
+++ b/src/hydro_io.h
@@ -43,6 +43,8 @@
 #include "./hydro/Shadowswift/hydro_io.h"
 #elif defined(PLANETARY_SPH)
 #include "./hydro/Planetary/hydro_io.h"
+#elif defined(REMIX_SPH)
+#include "./hydro/REMIX/hydro_io.h"
 #elif defined(SPHENIX_SPH)
 #include "./hydro/SPHENIX/hydro_io.h"
 #elif defined(GASOLINE_SPH)
diff --git a/src/hydro_parameters.h b/src/hydro_parameters.h
index 645a5fe5af9be5b67e4e1c9f17acc13c95938d96..46d93ad43a212f3db84597d3a4cfccbaff834605 100644
--- a/src/hydro_parameters.h
+++ b/src/hydro_parameters.h
@@ -52,6 +52,8 @@
 #include "./hydro/Shadowswift/hydro_parameters.h"
 #elif defined(PLANETARY_SPH)
 #include "./hydro/Planetary/hydro_parameters.h"
+#elif defined(REMIX_SPH)
+#include "./hydro/REMIX/hydro_parameters.h"
 #elif defined(SPHENIX_SPH)
 #include "./hydro/SPHENIX/hydro_parameters.h"
 #elif defined(GASOLINE_SPH)
diff --git a/src/part.h b/src/part.h
index bb574651b5a1fdb2005beeeacdc00208adb09f96..c68c91cfa804e476cc41cd26db55d5e1397790e2 100644
--- a/src/part.h
+++ b/src/part.h
@@ -80,6 +80,11 @@ struct threadpool;
 #elif defined(PLANETARY_SPH)
 #include "./hydro/Planetary/hydro_part.h"
 #define hydro_need_extra_init_loop 0
+#elif defined(REMIX_SPH)
+#include "./hydro/REMIX/hydro_part.h"
+#define hydro_need_extra_init_loop 0
+#define EXTRA_HYDRO_LOOP
+#define EXTRA_HYDRO_LOOP_TYPE2
 #elif defined(SPHENIX_SPH)
 #include "./hydro/SPHENIX/hydro_part.h"
 #define hydro_need_extra_init_loop 0
diff --git a/src/runner_main.c b/src/runner_main.c
index 8b2d268144899bb32a614d28d7e919cae6eccbbc..e6b3b1ef0510b952164487dba5e511899e836795 100644
--- a/src/runner_main.c
+++ b/src/runner_main.c
@@ -214,8 +214,13 @@ void *runner_main(void *data) {
                                           /*limit_h_max=*/0);
 #ifdef EXTRA_HYDRO_LOOP
           else if (t->subtype == task_subtype_gradient)
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+            runner_doself2_branch_gradient(r, ci, /*limit_h_min=*/0,
+                                           /*limit_h_max=*/0);
+#else
             runner_doself1_branch_gradient(r, ci, /*limit_h_min=*/0,
                                            /*limit_h_max=*/0);
+#endif
 #endif
           else if (t->subtype == task_subtype_force)
             runner_doself2_branch_force(r, ci, /*limit_h_min=*/0,
@@ -274,8 +279,13 @@ void *runner_main(void *data) {
                                           /*limit_h_max=*/0);
 #ifdef EXTRA_HYDRO_LOOP
           else if (t->subtype == task_subtype_gradient)
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+            runner_dopair2_branch_gradient(r, ci, cj, /*limit_h_min=*/0,
+                                           /*limit_h_max=*/0);
+#else
             runner_dopair1_branch_gradient(r, ci, cj, /*limit_h_min=*/0,
                                            /*limit_h_max=*/0);
+#endif
 #endif
           else if (t->subtype == task_subtype_force)
             runner_dopair2_branch_force(r, ci, cj, /*limit_h_min=*/0,
@@ -331,7 +341,11 @@ void *runner_main(void *data) {
             runner_dosub_self1_density(r, ci, /*below_h_max=*/0, 1);
 #ifdef EXTRA_HYDRO_LOOP
           else if (t->subtype == task_subtype_gradient)
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+            runner_dosub_self2_gradient(r, ci, /*below_h_max=*/0, 1);
+#else
             runner_dosub_self1_gradient(r, ci, /*below_h_max=*/0, 1);
+#endif
 #endif
           else if (t->subtype == task_subtype_force)
             runner_dosub_self2_force(r, ci, /*below_h_max=*/0, 1);
@@ -379,7 +393,11 @@ void *runner_main(void *data) {
             runner_dosub_pair1_density(r, ci, cj, /*below_h_max=*/0, 1);
 #ifdef EXTRA_HYDRO_LOOP
           else if (t->subtype == task_subtype_gradient)
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+            runner_dosub_pair2_gradient(r, ci, cj, /*below_h_max=*/0, 1);
+#else
             runner_dosub_pair1_gradient(r, ci, cj, /*below_h_max=*/0, 1);
+#endif
 #endif
           else if (t->subtype == task_subtype_force)
             runner_dosub_pair2_force(r, ci, cj, /*below_h_max=*/0, 1);
diff --git a/src/symmetric_matrix.h b/src/symmetric_matrix.h
new file mode 100644
index 0000000000000000000000000000000000000000..88c82df542881a43e6332892f82d2dfb9e94e69a
--- /dev/null
+++ b/src/symmetric_matrix.h
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * This file is part of SWIFT.
+ * Copyright (c) 2023  Matthieu Schaller (schaller@strw.leidenuniv.nl)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ ******************************************************************************/
+#ifndef SWIFT_SYMMETRIC_MATRIX_H
+#define SWIFT_SYMMETRIC_MATRIX_H
+
+/* Local includes */
+#include "dimension.h"
+#include "error.h"
+
+/**
+ * @brief Symmetric matrix definition in 3D.
+ *
+ * The matrix elements can be accessed as an array or via their "coordinates".
+ * Replicated elements are not stored.
+ */
+struct sym_matrix {
+
+  union {
+    struct {
+      float elements[6];
+    };
+    struct {
+      float xx;
+      float yy;
+      float zz;
+      float xy;
+      float xz;
+      float yz;
+    };
+  };
+};
+
+/**
+ * @brief Zero the matrix
+ */
+__attribute__((always_inline)) INLINE static void zero_sym_matrix(
+    struct sym_matrix *M) {
+  for (int i = 0; i < 6; ++i) M->elements[i] = 0.f;
+}
+
+/**
+ * @brief Construct a 3x3 array from a symmetric matrix.
+ */
+__attribute__((always_inline)) INLINE static void get_matrix_from_sym_matrix(
+    float out[3][3], const struct sym_matrix *in) {
+
+  out[0][0] = in->xx;
+  out[0][1] = in->xy;
+  out[0][2] = in->xz;
+  out[1][0] = in->xy;
+  out[1][1] = in->yy;
+  out[1][2] = in->yz;
+  out[2][0] = in->xz;
+  out[2][1] = in->yz;
+  out[2][2] = in->zz;
+}
+
+/**
+ * @brief Construct a symmetric matrix from a 3x3 array.
+ *
+ * No check is performed to verify the input 3x3 array is indeed symmetric.
+ */
+__attribute__((always_inline)) INLINE static void get_sym_matrix_from_matrix(
+    struct sym_matrix *out, const float in[3][3]) {
+  out->xx = in[0][0];
+  out->yy = in[1][1];
+  out->zz = in[2][2];
+  out->xy = in[0][1];
+  out->xz = in[0][2];
+  out->yz = in[1][2];
+}
+
+/**
+ * @brief Compute the product of a symmetric matrix and a vector.
+ */
+__attribute__((always_inline)) INLINE static void sym_matrix_multiply_by_vector(
+    float out[3], const struct sym_matrix *M, const float v[3]) {
+
+  out[0] = M->xx * v[0] + M->xy * v[1] + M->xz * v[2];
+  out[1] = M->xy * v[0] + M->yy * v[1] + M->yz * v[2];
+  out[2] = M->xz * v[0] + M->yz * v[1] + M->zz * v[2];
+}
+
+/**
+ * @brief Multiply two symmetric matrices in two operations, ABA.
+ */
+__attribute__((always_inline)) INLINE static void sym_matrix_multiplication_ABA(
+    struct sym_matrix *M_out, const struct sym_matrix *A,
+    const struct sym_matrix *B) {
+
+  float BA_array[3][3] = {0};
+  float A_array[3][3], B_array[3][3];
+  get_matrix_from_sym_matrix(A_array, A);
+  get_matrix_from_sym_matrix(B_array, B);
+  for (int i = 0; i < 3; i++) {
+    for (int j = 0; j < 3; j++) {
+      for (int k = 0; k < 3; k++) {
+        BA_array[i][j] += B_array[i][k] * A_array[k][j];
+      }
+    }
+  }
+
+  M_out->xx = (A->xx * BA_array[0][0] + A->xy * BA_array[1][0] +
+               A->xz * BA_array[2][0]);
+  M_out->yy = (A->xy * BA_array[0][1] + A->yy * BA_array[1][1] +
+               A->yz * BA_array[2][1]);
+  M_out->zz = (A->xz * BA_array[0][2] + A->yz * BA_array[1][2] +
+               A->zz * BA_array[2][2]);
+  M_out->xy = (A->xy * BA_array[0][0] + A->yy * BA_array[1][0] +
+               A->yz * BA_array[2][0]);
+  M_out->xz = (A->xz * BA_array[0][0] + A->yz * BA_array[1][0] +
+               A->zz * BA_array[2][0]);
+  M_out->yz = (A->xz * BA_array[0][1] + A->yz * BA_array[1][1] +
+               A->zz * BA_array[2][1]);
+}
+
+/**
+ * @brief Print a symmetric matrix.
+ */
+__attribute__((always_inline)) INLINE static void sym_matrix_print(
+    const struct sym_matrix *M) {
+  message("|%.4f %.4f %.4f|", M->xx, M->xy, M->xz);
+  message("|%.4f %.4f %.4f|", M->xy, M->yy, M->yz);
+  message("|%.4f %.4f %.4f|", M->xz, M->yz, M->zz);
+}
+
+/**
+ * @brief Compute the inverse of a symmetric matrix and a vector.
+ *
+ * Returned as a symmetric matrix
+ */
+__attribute__((always_inline)) INLINE static void sym_matrix_invert(
+    struct sym_matrix *M_inv, const struct sym_matrix *M) {
+
+  float M_inv_matrix[3][3];
+  get_matrix_from_sym_matrix(M_inv_matrix, M);
+  int res = invert_dimension_by_dimension_matrix(M_inv_matrix);
+  if (res) {
+    sym_matrix_print(M);
+    error("Error inverting matrix");
+  }
+
+  M_inv->xx = M_inv_matrix[0][0];
+  M_inv->yy = M_inv_matrix[1][1];
+  M_inv->zz = M_inv_matrix[2][2];
+  M_inv->xy = M_inv_matrix[0][1];
+  M_inv->xz = M_inv_matrix[0][2];
+  M_inv->yz = M_inv_matrix[1][2];
+}
+
+#endif /* SWIFT_SYMMETRIC_MATRIX_H */
diff --git a/src/tools.c b/src/tools.c
index 65d7d976ccc060e8a2111acf88a3a3cc862811bf..44c8fbdc39dc51c5dc6361b0a9a5936d46f63054 100644
--- a/src/tools.c
+++ b/src/tools.c
@@ -55,6 +55,7 @@
 #include "runner.h"
 #include "sink_iact.h"
 #include "sink_properties.h"
+#include "space_getsid.h"
 #include "star_formation_iact.h"
 #include "stars.h"
 
@@ -205,13 +206,18 @@ void pairs_single_density(double *dim, long long int pid,
 
 void pairs_all_density(struct runner *r, struct cell *ci, struct cell *cj) {
 
-  float r2, hi, hj, hig2, hjg2, dx[3];
+  float hi, hj, hig2, hjg2;
   struct part *pi, *pj;
   const double dim[3] = {r->e->s->dim[0], r->e->s->dim[1], r->e->s->dim[2]};
   const struct engine *e = r->e;
   const struct cosmology *cosmo = e->cosmology;
   const float a = cosmo->a;
   const float H = cosmo->H;
+  double shift[3] = {0.0, 0.0, 0.0};
+  space_getsid_and_swap_cells(e->s, &ci, &cj, shift);
+  const double shift_i[3] = {cj->loc[0] + shift[0], cj->loc[1] + shift[1],
+                             cj->loc[2] + shift[2]};
+  const double shift_j[3] = {cj->loc[0], cj->loc[1], cj->loc[2]};
 
   /* Implements a double-for loop and checks every interaction */
   for (int i = 0; i < ci->hydro.count; ++i) {
@@ -223,17 +229,23 @@ void pairs_all_density(struct runner *r, struct cell *ci, struct cell *cj) {
     /* Skip inactive particles. */
     if (!part_is_active(pi, e)) continue;
 
+    const float pix = pi->x[0] - shift_i[0];
+    const float piy = pi->x[1] - shift_i[1];
+    const float piz = pi->x[2] - shift_i[2];
+
     for (int j = 0; j < cj->hydro.count; ++j) {
 
       pj = &cj->hydro.parts[j];
 
+      const float pjx = pj->x[0] - shift_j[0];
+      const float pjy = pj->x[1] - shift_j[1];
+      const float pjz = pj->x[2] - shift_j[2];
+
       /* Pairwise distance */
-      r2 = 0.0f;
-      for (int k = 0; k < 3; k++) {
-        dx[k] = ci->hydro.parts[i].x[k] - cj->hydro.parts[j].x[k];
-        dx[k] = nearest(dx[k], dim[k]);
-        r2 += dx[k] * dx[k];
-      }
+      const float dx[3] = {nearest(pix - pjx, dim[0]),
+                           nearest(piy - pjy, dim[1]),
+                           nearest(piz - pjz, dim[2])};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Hit or miss? */
       if (r2 < hig2 && !part_is_inhibited(pj, e)) {
@@ -258,17 +270,23 @@ void pairs_all_density(struct runner *r, struct cell *ci, struct cell *cj) {
     /* Skip inactive particles. */
     if (!part_is_active(pj, e)) continue;
 
+    const float pjx = pj->x[0] - shift_j[0];
+    const float pjy = pj->x[1] - shift_j[1];
+    const float pjz = pj->x[2] - shift_j[2];
+
     for (int i = 0; i < ci->hydro.count; ++i) {
 
       pi = &ci->hydro.parts[i];
 
+      const float pix = pi->x[0] - shift_i[0];
+      const float piy = pi->x[1] - shift_i[1];
+      const float piz = pi->x[2] - shift_i[2];
+
       /* Pairwise distance */
-      r2 = 0.0f;
-      for (int k = 0; k < 3; k++) {
-        dx[k] = cj->hydro.parts[j].x[k] - ci->hydro.parts[i].x[k];
-        dx[k] = nearest(dx[k], dim[k]);
-        r2 += dx[k] * dx[k];
-      }
+      const float dx[3] = {nearest(pjx - pix, dim[0]),
+                           nearest(pjy - piy, dim[1]),
+                           nearest(pjz - piz, dim[2])};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Hit or miss? */
       if (r2 < hjg2 && !part_is_inhibited(pi, e)) {
@@ -287,13 +305,18 @@ void pairs_all_density(struct runner *r, struct cell *ci, struct cell *cj) {
 #ifdef EXTRA_HYDRO_LOOP
 void pairs_all_gradient(struct runner *r, struct cell *ci, struct cell *cj) {
 
-  float r2, hi, hj, hig2, hjg2, dx[3];
+  float hi, hj, hig2, hjg2;
   struct part *pi, *pj;
   const double dim[3] = {r->e->s->dim[0], r->e->s->dim[1], r->e->s->dim[2]};
   const struct engine *e = r->e;
   const struct cosmology *cosmo = e->cosmology;
   const float a = cosmo->a;
   const float H = cosmo->H;
+  double shift[3] = {0.0, 0.0, 0.0};
+  space_getsid_and_swap_cells(e->s, &ci, &cj, shift);
+  const double shift_i[3] = {cj->loc[0] + shift[0], cj->loc[1] + shift[1],
+                             cj->loc[2] + shift[2]};
+  const double shift_j[3] = {cj->loc[0], cj->loc[1], cj->loc[2]};
 
   /* Implements a double-for loop and checks every interaction */
   for (int i = 0; i < ci->hydro.count; ++i) {
@@ -305,25 +328,39 @@ void pairs_all_gradient(struct runner *r, struct cell *ci, struct cell *cj) {
     /* Skip inactive particles. */
     if (!part_is_active(pi, e)) continue;
 
+    const float pix = pi->x[0] - shift_i[0];
+    const float piy = pi->x[1] - shift_i[1];
+    const float piz = pi->x[2] - shift_i[2];
+
     for (int j = 0; j < cj->hydro.count; ++j) {
 
       pj = &cj->hydro.parts[j];
       hj = pj->h;
       hjg2 = hj * hj * kernel_gamma2;
 
+      const float pjx = pj->x[0] - shift_j[0];
+      const float pjy = pj->x[1] - shift_j[1];
+      const float pjz = pj->x[2] - shift_j[2];
+
       /* Pairwise distance */
-      r2 = 0.0f;
-      for (int k = 0; k < 3; k++) {
-        dx[k] = ci->hydro.parts[i].x[k] - cj->hydro.parts[j].x[k];
-        dx[k] = nearest(dx[k], dim[k]);
-        r2 += dx[k] * dx[k];
-      }
+      const float dx[3] = {nearest(pix - pjx, dim[0]),
+                           nearest(piy - pjy, dim[1]),
+                           nearest(piz - pjz, dim[2])};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Hit or miss? */
-      if (r2 < hig2 && !part_is_inhibited(pj, e)) {
-
-        /* Interact */
-        runner_iact_nonsym_gradient(r2, dx, hi, hj, pi, pj, a, H);
+      if (!part_is_inhibited(pj, e)) {
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+        if (r2 < hig2 || r2 < hjg2) {
+          /* Interact */
+          runner_iact_nonsym_gradient(r2, dx, hi, hj, pi, pj, a, H);
+        }
+#else
+        if (r2 < hig2) {
+          /* Interact */
+          runner_iact_nonsym_gradient(r2, dx, hi, hj, pi, pj, a, H);
+        }
+#endif /* EXTRA_HYDRO_LOOP_TYPE2 */
       }
     }
   }
@@ -338,25 +375,39 @@ void pairs_all_gradient(struct runner *r, struct cell *ci, struct cell *cj) {
     /* Skip inactive particles. */
     if (!part_is_active(pj, e)) continue;
 
+    const float pjx = pj->x[0] - shift_j[0];
+    const float pjy = pj->x[1] - shift_j[1];
+    const float pjz = pj->x[2] - shift_j[2];
+
     for (int i = 0; i < ci->hydro.count; ++i) {
 
       pi = &ci->hydro.parts[i];
       hi = pi->h;
       hig2 = hi * hi * kernel_gamma2;
 
+      const float pix = pi->x[0] - shift_i[0];
+      const float piy = pi->x[1] - shift_i[1];
+      const float piz = pi->x[2] - shift_i[2];
+
       /* Pairwise distance */
-      r2 = 0.0f;
-      for (int k = 0; k < 3; k++) {
-        dx[k] = cj->hydro.parts[j].x[k] - ci->hydro.parts[i].x[k];
-        dx[k] = nearest(dx[k], dim[k]);
-        r2 += dx[k] * dx[k];
-      }
+      const float dx[3] = {nearest(pjx - pix, dim[0]),
+                           nearest(pjy - piy, dim[1]),
+                           nearest(pjz - piz, dim[2])};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Hit or miss? */
-      if (r2 < hjg2 && !part_is_inhibited(pi, e)) {
-
-        /* Interact */
-        runner_iact_nonsym_gradient(r2, dx, hj, pi->h, pj, pi, a, H);
+      if (!part_is_inhibited(pi, e)) {
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+        if (r2 < hig2 || r2 < hjg2) {
+          /* Interact */
+          runner_iact_nonsym_gradient(r2, dx, hj, pi->h, pj, pi, a, H);
+        }
+#else
+        if (r2 < hjg2) {
+          /* Interact */
+          runner_iact_nonsym_gradient(r2, dx, hj, pi->h, pj, pi, a, H);
+        }
+#endif /* EXTRA_HYDRO_LOOP_TYPE2 */
       }
     }
   }
@@ -365,13 +416,18 @@ void pairs_all_gradient(struct runner *r, struct cell *ci, struct cell *cj) {
 
 void pairs_all_force(struct runner *r, struct cell *ci, struct cell *cj) {
 
-  float r2, hi, hj, hig2, hjg2, dx[3];
+  float hi, hj, hig2, hjg2;
   struct part *pi, *pj;
   const double dim[3] = {r->e->s->dim[0], r->e->s->dim[1], r->e->s->dim[2]};
   const struct engine *e = r->e;
   const struct cosmology *cosmo = e->cosmology;
   const float a = cosmo->a;
   const float H = cosmo->H;
+  double shift[3] = {0.0, 0.0, 0.0};
+  space_getsid_and_swap_cells(e->s, &ci, &cj, shift);
+  const double shift_i[3] = {cj->loc[0] + shift[0], cj->loc[1] + shift[1],
+                             cj->loc[2] + shift[2]};
+  const double shift_j[3] = {cj->loc[0], cj->loc[1], cj->loc[2]};
 
   /* Implements a double-for loop and checks every interaction */
   for (int i = 0; i < ci->hydro.count; ++i) {
@@ -383,19 +439,25 @@ void pairs_all_force(struct runner *r, struct cell *ci, struct cell *cj) {
     /* Skip inactive particles. */
     if (!part_is_active(pi, e)) continue;
 
+    const float pix = pi->x[0] - shift_i[0];
+    const float piy = pi->x[1] - shift_i[1];
+    const float piz = pi->x[2] - shift_i[2];
+
     for (int j = 0; j < cj->hydro.count; ++j) {
 
       pj = &cj->hydro.parts[j];
       hj = pj->h;
       hjg2 = hj * hj * kernel_gamma2;
 
+      const float pjx = pj->x[0] - shift_j[0];
+      const float pjy = pj->x[1] - shift_j[1];
+      const float pjz = pj->x[2] - shift_j[2];
+
       /* Pairwise distance */
-      r2 = 0.0f;
-      for (int k = 0; k < 3; k++) {
-        dx[k] = ci->hydro.parts[i].x[k] - cj->hydro.parts[j].x[k];
-        dx[k] = nearest(dx[k], dim[k]);
-        r2 += dx[k] * dx[k];
-      }
+      const float dx[3] = {nearest(pix - pjx, dim[0]),
+                           nearest(piy - pjy, dim[1]),
+                           nearest(piz - pjz, dim[2])};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Hit or miss? */
       if (r2 < hig2 || r2 < hjg2) {
@@ -416,19 +478,25 @@ void pairs_all_force(struct runner *r, struct cell *ci, struct cell *cj) {
     /* Skip inactive particles. */
     if (!part_is_active(pj, e)) continue;
 
+    const float pjx = pj->x[0] - shift_j[0];
+    const float pjy = pj->x[1] - shift_j[1];
+    const float pjz = pj->x[2] - shift_j[2];
+
     for (int i = 0; i < ci->hydro.count; ++i) {
 
       pi = &ci->hydro.parts[i];
       hi = pi->h;
       hig2 = hi * hi * kernel_gamma2;
 
+      const float pix = pi->x[0] - shift_i[0];
+      const float piy = pi->x[1] - shift_i[1];
+      const float piz = pi->x[2] - shift_i[2];
+
       /* Pairwise distance */
-      r2 = 0.0f;
-      for (int k = 0; k < 3; k++) {
-        dx[k] = cj->hydro.parts[j].x[k] - ci->hydro.parts[i].x[k];
-        dx[k] = nearest(dx[k], dim[k]);
-        r2 += dx[k] * dx[k];
-      }
+      const float dx[3] = {nearest(pjx - pix, dim[0]),
+                           nearest(pjy - piy, dim[1]),
+                           nearest(pjz - piz, dim[2])};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Hit or miss? */
       if (r2 < hjg2 || r2 < hig2) {
@@ -520,7 +588,7 @@ void pairs_all_stars_density(struct runner *r, struct cell *ci,
 }
 
 void self_all_density(struct runner *r, struct cell *ci) {
-  float r2, hi, hj, hig2, hjg2, dxi[3];  //, dxj[3];
+  float hi, hj, hig2, hjg2;
   struct part *pi, *pj;
   const struct engine *e = r->e;
   const struct cosmology *cosmo = e->cosmology;
@@ -534,6 +602,8 @@ void self_all_density(struct runner *r, struct cell *ci) {
     hi = pi->h;
     hig2 = hi * hi * kernel_gamma2;
 
+    const double pix[3] = {pi->x[0], pi->x[1], pi->x[2]};
+
     for (int j = i + 1; j < ci->hydro.count; ++j) {
 
       pj = &ci->hydro.parts[j];
@@ -542,37 +612,37 @@ void self_all_density(struct runner *r, struct cell *ci) {
 
       if (pi == pj) continue;
 
+      const double pjx[3] = {pj->x[0], pj->x[1], pj->x[2]};
+
       /* Pairwise distance */
-      r2 = 0.0f;
-      for (int k = 0; k < 3; k++) {
-        dxi[k] = ci->hydro.parts[i].x[k] - ci->hydro.parts[j].x[k];
-        r2 += dxi[k] * dxi[k];
-      }
+      float dx[3] = {(float)(pix[0] - pjx[0]), (float)(pix[1] - pjx[1]),
+                     (float)(pix[2] - pjx[2])};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Hit or miss? */
       if (r2 < hig2 && part_is_active(pi, e) && !part_is_inhibited(pj, e)) {
 
         /* Interact */
-        runner_iact_nonsym_density(r2, dxi, hi, hj, pi, pj, a, H);
-        runner_iact_nonsym_chemistry(r2, dxi, hi, hj, pi, pj, a, H);
-        runner_iact_nonsym_pressure_floor(r2, dxi, hi, hj, pi, pj, a, H);
-        runner_iact_nonsym_star_formation(r2, dxi, hi, hj, pi, pj, a, H);
-        runner_iact_nonsym_sink(r2, dxi, hi, hj, pi, pj, a, H);
+        runner_iact_nonsym_density(r2, dx, hi, hj, pi, pj, a, H);
+        runner_iact_nonsym_chemistry(r2, dx, hi, hj, pi, pj, a, H);
+        runner_iact_nonsym_pressure_floor(r2, dx, hi, hj, pi, pj, a, H);
+        runner_iact_nonsym_star_formation(r2, dx, hi, hj, pi, pj, a, H);
+        runner_iact_nonsym_sink(r2, dx, hi, hj, pi, pj, a, H);
       }
 
       /* Hit or miss? */
       if (r2 < hjg2 && part_is_active(pj, e) && !part_is_inhibited(pi, e)) {
 
-        dxi[0] = -dxi[0];
-        dxi[1] = -dxi[1];
-        dxi[2] = -dxi[2];
+        dx[0] = -dx[0];
+        dx[1] = -dx[1];
+        dx[2] = -dx[2];
 
         /* Interact */
-        runner_iact_nonsym_density(r2, dxi, hj, hi, pj, pi, a, H);
-        runner_iact_nonsym_chemistry(r2, dxi, hj, hi, pj, pi, a, H);
-        runner_iact_nonsym_pressure_floor(r2, dxi, hj, hi, pj, pi, a, H);
-        runner_iact_nonsym_star_formation(r2, dxi, hj, hi, pj, pi, a, H);
-        runner_iact_nonsym_sink(r2, dxi, hj, hi, pj, pi, a, H);
+        runner_iact_nonsym_density(r2, dx, hj, hi, pj, pi, a, H);
+        runner_iact_nonsym_chemistry(r2, dx, hj, hi, pj, pi, a, H);
+        runner_iact_nonsym_pressure_floor(r2, dx, hj, hi, pj, pi, a, H);
+        runner_iact_nonsym_star_formation(r2, dx, hj, hi, pj, pi, a, H);
+        runner_iact_nonsym_sink(r2, dx, hj, hi, pj, pi, a, H);
       }
     }
   }
@@ -580,7 +650,7 @@ void self_all_density(struct runner *r, struct cell *ci) {
 
 #ifdef EXTRA_HYDRO_LOOP
 void self_all_gradient(struct runner *r, struct cell *ci) {
-  float r2, hi, hj, hig2, hjg2, dxi[3];  //, dxj[3];
+  float hi, hj, hig2, hjg2;
   struct part *pi, *pj;
   const struct engine *e = r->e;
   const struct cosmology *cosmo = e->cosmology;
@@ -594,6 +664,8 @@ void self_all_gradient(struct runner *r, struct cell *ci) {
     hi = pi->h;
     hig2 = hi * hi * kernel_gamma2;
 
+    const double pix[3] = {pi->x[0], pi->x[1], pi->x[2]};
+
     for (int j = i + 1; j < ci->hydro.count; ++j) {
 
       pj = &ci->hydro.parts[j];
@@ -602,29 +674,46 @@ void self_all_gradient(struct runner *r, struct cell *ci) {
 
       if (pi == pj) continue;
 
+      const double pjx[3] = {pj->x[0], pj->x[1], pj->x[2]};
+
       /* Pairwise distance */
-      r2 = 0.0f;
-      for (int k = 0; k < 3; k++) {
-        dxi[k] = ci->hydro.parts[i].x[k] - ci->hydro.parts[j].x[k];
-        r2 += dxi[k] * dxi[k];
-      }
+      float dx[3] = {(float)(pix[0] - pjx[0]), (float)(pix[1] - pjx[1]),
+                     (float)(pix[2] - pjx[2])};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Hit or miss? */
-      if (r2 < hig2 && part_is_active(pi, e) && !part_is_inhibited(pj, e)) {
-
-        /* Interact */
-        runner_iact_nonsym_gradient(r2, dxi, hi, hj, pi, pj, a, H);
+      if (part_is_active(pi, e) && !part_is_inhibited(pj, e)) {
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+        if (r2 < hig2 || r2 < hjg2) {
+          /* Interact */
+          runner_iact_nonsym_gradient(r2, dx, hi, hj, pi, pj, a, H);
+        }
+#else
+        if (r2 < hig2) {
+          /* Interact */
+          runner_iact_nonsym_gradient(r2, dx, hi, hj, pi, pj, a, H);
+        }
+#endif /* EXTRA_HYDRO_LOOP_TYPE2 */
       }
 
       /* Hit or miss? */
-      if (r2 < hjg2 && part_is_active(pj, e) && !part_is_inhibited(pi, e)) {
+      if (part_is_active(pj, e) && !part_is_inhibited(pi, e)) {
 
-        dxi[0] = -dxi[0];
-        dxi[1] = -dxi[1];
-        dxi[2] = -dxi[2];
+        dx[0] = -dx[0];
+        dx[1] = -dx[1];
+        dx[2] = -dx[2];
 
-        /* Interact */
-        runner_iact_nonsym_gradient(r2, dxi, hj, hi, pj, pi, a, H);
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+        if (r2 < hig2 || r2 < hjg2) {
+          /* Interact */
+          runner_iact_nonsym_gradient(r2, dx, hj, hi, pj, pi, a, H);
+        }
+#else
+        if (r2 < hjg2) {
+          /* Interact */
+          runner_iact_nonsym_gradient(r2, dx, hj, hi, pj, pi, a, H);
+        }
+#endif /* EXTRA_HYDRO_LOOP_TYPE2 */
       }
     }
   }
@@ -632,7 +721,7 @@ void self_all_gradient(struct runner *r, struct cell *ci) {
 #endif /* EXTRA_HYDRO_LOOP */
 
 void self_all_force(struct runner *r, struct cell *ci) {
-  float r2, hi, hj, hig2, hjg2, dxi[3];  //, dxj[3];
+  float hi, hj, hig2, hjg2;
   struct part *pi, *pj;
   const struct engine *e = r->e;
   const struct cosmology *cosmo = e->cosmology;
@@ -646,6 +735,8 @@ void self_all_force(struct runner *r, struct cell *ci) {
     hi = pi->h;
     hig2 = hi * hi * kernel_gamma2;
 
+    const double pix[3] = {pi->x[0], pi->x[1], pi->x[2]};
+
     for (int j = i + 1; j < ci->hydro.count; ++j) {
 
       pj = &ci->hydro.parts[j];
@@ -654,18 +745,18 @@ void self_all_force(struct runner *r, struct cell *ci) {
 
       if (pi == pj) continue;
 
+      const double pjx[3] = {pj->x[0], pj->x[1], pj->x[2]};
+
       /* Pairwise distance */
-      r2 = 0.0f;
-      for (int k = 0; k < 3; k++) {
-        dxi[k] = ci->hydro.parts[i].x[k] - ci->hydro.parts[j].x[k];
-        r2 += dxi[k] * dxi[k];
-      }
+      float dx[3] = {(float)(pix[0] - pjx[0]), (float)(pix[1] - pjx[1]),
+                     (float)(pix[2] - pjx[2])};
+      const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
 
       /* Hit or miss? */
       if (r2 < hig2 || r2 < hjg2) {
 
         /* Interact */
-        runner_iact_force(r2, dxi, hi, hj, pi, pj, a, H);
+        runner_iact_force(r2, dx, hi, hj, pi, pj, a, H);
       }
     }
   }
diff --git a/tests/makeInput.py b/tests/makeInput.py
index c3544d7a25f93ec5c914754db2a93d8a6b30b7c4..43fe3da976e28e0cdb00c6ade0dcf27c21cb642f 100644
--- a/tests/makeInput.py
+++ b/tests/makeInput.py
@@ -27,18 +27,19 @@ from numpy import *
 periodic = 1  # 1 For periodic box
 boxSize = 1.0
 L = 4  # Number of particles along one axis
-rho = 2.0  # Density
+density = 2.0  # Density
 P = 1.0  # Pressure
 gamma = 5.0 / 3.0  # Gas adiabatic index
+material = 0 # Ideal gas
 fileName = "input.hdf5"
 
 # ---------------------------------------------------
 numPart = L ** 3
-mass = boxSize ** 3 * rho / numPart
-internalEnergy = P / ((gamma - 1.0) * rho)
+mass = boxSize ** 3 * density / numPart
+internalEnergy = P / ((gamma - 1.0) * density)
 
 # chemistry data
-he_density = rho * 0.24
+he_density = density * 0.24
 
 # Generate particles
 coords = zeros((numPart, 3))
@@ -46,7 +47,9 @@ v = zeros((numPart, 3))
 m = zeros((numPart, 1))
 h = zeros((numPart, 1))
 u = zeros((numPart, 1))
+rho = zeros((numPart, 1))
 ids = zeros((numPart, 1), dtype="L")
+mat = zeros((numPart, 1), dtype="i")
 
 # chemistry data
 he = zeros((numPart, 1))
@@ -67,7 +70,9 @@ for i in range(L):
             m[index] = mass
             h[index] = 2.251 * boxSize / L
             u[index] = internalEnergy
+            rho[index] = density
             ids[index] = index
+            mat[index] = material
             # chemistry data
             he[index] = he_density
 
@@ -114,8 +119,12 @@ ds = grp.create_dataset("SmoothingLength", (numPart, 1), "f")
 ds[()] = h
 ds = grp.create_dataset("InternalEnergy", (numPart, 1), "f")
 ds[()] = u
+ds = grp.create_dataset("Density", (numPart, 1), "f")
+ds[()] = rho
 ds = grp.create_dataset("ParticleIDs", (numPart, 1), "L")
 ds[()] = ids
+ds = grp.create_dataset("MaterialIDs", (numPart, 1), "i")
+ds[()] = mat
 # chemistry
 ds = grp.create_dataset("HeDensity", (numPart, 1), "f")
 ds[()] = he
diff --git a/tests/test125cells.c b/tests/test125cells.c
index 063d414098e2a82f45a5679eb19cc53be36bd00e..128c36480fb36723b713a0b3e98e8bdd83fc9559 100644
--- a/tests/test125cells.c
+++ b/tests/test125cells.c
@@ -115,7 +115,9 @@ void set_energy_state(struct part *part, enum pressure_field press, float size,
     defined(HOPKINS_PU_SPH_MONAGHAN) || defined(ANARCHY_PU_SPH) || \
     defined(SPHENIX_SPH) || defined(PHANTOM_SPH) || defined(GASOLINE_SPH)
   part->u = pressure / (hydro_gamma_minus_one * density);
-#elif defined(PLANETARY_SPH)
+#elif defined(PLANETARY_SPH) || defined(REMIX_SPH)
+  set_idg_def(&eos.idg_def, 0);
+  part->mat_id = 0;
   part->u = pressure / (hydro_gamma_minus_one * density);
 #elif defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH)
   part->conserved.energy = pressure / (hydro_gamma_minus_one * density);
@@ -219,6 +221,11 @@ void reset_particles(struct cell *c, struct hydro_space *hs,
     hydro_first_init_part(p, &c->hydro.xparts[i]);
     p->time_bin = 1;
 #endif
+#if defined(REMIX_SPH)
+    p->rho = density;
+    hydro_first_init_part(p, &c->hydro.xparts[i]);
+    p->time_bin = 1;
+#endif
 
     hydro_init_part(p, hs);
     adaptive_softening_init_part(p);
@@ -289,6 +296,9 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h,
 #else
         part->mass = density * volume / count;
 #endif
+#if defined(REMIX_SPH)
+        part->rho = density;
+#endif
 
         set_velocity(part, vel, size);
         set_energy_state(part, press, size, density);
@@ -379,7 +389,7 @@ void dump_particle_fields(char *fileName, struct cell *main_cell,
 #if defined(MINIMAL_SPH) || defined(PLANETARY_SPH) ||              \
     defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH) ||            \
     defined(HOPKINS_PU_SPH) || defined(HOPKINS_PU_SPH_MONAGHAN) || \
-    defined(GASOLINE_SPH)
+    defined(GASOLINE_SPH) || defined(REMIX_SPH)
             0.f,
 #elif defined(ANARCHY_PU_SPH) || defined(SPHENIX_SPH) || defined(PHANTOM_SPH)
             main_cell->hydro.parts[pid].viscosity.div_v,
@@ -442,11 +452,19 @@ void runner_dopair1_branch_density(struct runner *r, struct cell *ci,
 void runner_doself1_branch_density(struct runner *r, struct cell *ci,
                                    int limit_h_min, int limit_h_max);
 #ifdef EXTRA_HYDRO_LOOP
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+void runner_dopair2_branch_gradient(struct runner *r, struct cell *ci,
+                                    struct cell *cj, int limit_h_min,
+                                    int limit_h_max);
+void runner_doself2_branch_gradient(struct runner *r, struct cell *ci,
+                                    int limit_h_min, int limit_h_max);
+#else
 void runner_dopair1_branch_gradient(struct runner *r, struct cell *ci,
                                     struct cell *cj, int limit_h_min,
                                     int limit_h_max);
 void runner_doself1_branch_gradient(struct runner *r, struct cell *ci,
                                     int limit_h_min, int limit_h_max);
+#endif /* EXTRA_HYDRO_LOOP_TYPE2 */
 #endif /* EXTRA_HYDRO LOOP */
 void runner_dopair2_branch_force(struct runner *r, struct cell *ci,
                                  struct cell *cj, int limit_h_min,
@@ -750,9 +768,15 @@ int main(int argc, char *argv[]) {
 
                 struct cell *cj = cells[iii * 25 + jjj * 5 + kkk];
 
-                if (cj > ci)
+                if (cj > ci) {
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+                  runner_dopair2_branch_gradient(
+                      &runner, ci, cj, /*limit_h_min=*/0, /*limit_h_max=*/0);
+#else
                   runner_dopair1_branch_gradient(
                       &runner, ci, cj, /*limit_h_min=*/0, /*limit_h_max=*/0);
+#endif
+                }
               }
             }
           }
@@ -761,9 +785,15 @@ int main(int argc, char *argv[]) {
     }
 
     /* And now the self-interaction for the central cells */
-    for (int j = 0; j < 27; ++j)
+    for (int j = 0; j < 27; ++j) {
+#ifdef EXTRA_HYDRO_LOOP_TYPE2
+      runner_doself2_branch_gradient(&runner, inner_cells[j], /*limit_h_min=*/0,
+                                     /*limit_h_max=*/0);
+#else
       runner_doself1_branch_gradient(&runner, inner_cells[j], /*limit_h_min=*/0,
                                      /*limit_h_max=*/0);
+#endif
+    }
 
     /* Extra ghost to finish everything on the central cells */
     for (int j = 0; j < 27; ++j)
diff --git a/tests/test27cells.c b/tests/test27cells.c
index 1a91613f6ae6d3d770ca45252c46544a198f7056..e90d4e1781058c66c71390b357486749ec39c176 100644
--- a/tests/test27cells.c
+++ b/tests/test27cells.c
@@ -160,6 +160,9 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
 #else
         part->mass = density * volume / count;
 #endif
+#if defined(REMIX_SPH)
+        part->rho_evol = density;
+#endif
 
 #if defined(HOPKINS_PE_SPH)
         part->entropy = 1.f;
diff --git a/tests/testActivePair.c b/tests/testActivePair.c
index d2eb6b29ebe8eb5bb06a6904f7027b70b799eb1c..1a9f5e849c89731941138957fb51c590a7240219 100644
--- a/tests/testActivePair.c
+++ b/tests/testActivePair.c
@@ -128,6 +128,15 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
         part->entropy_one_over_gamma = 1.f;
 #elif defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH)
         part->conserved.energy = 1.f;
+#elif defined(PLANETARY_SPH)
+        set_idg_def(&eos.idg_def, 0);
+        part->mat_id = 0;
+        part->u = 1.f;
+#elif defined(REMIX_SPH)
+        set_idg_def(&eos.idg_def, 0);
+        part->mat_id = 0;
+        part->u = 1.f;
+        part->rho_evol = 1.f;
 #endif
 
 #if defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH)
@@ -282,6 +291,44 @@ void zero_particle_fields_force(
     }
     p->geometry.volume = 1.0f;
 #endif
+#ifdef PLANETARY_SPH
+    p->rho = 1.f;
+    p->density.rho_dh = 0.f;
+    p->density.wcount = 48.f / (kernel_norm * pow_dimension(p->h));
+    p->density.wcount_dh = 0.f;
+    p->density.rot_v[0] = 0.f;
+    p->density.rot_v[1] = 0.f;
+    p->density.rot_v[2] = 0.f;
+    p->density.div_v = 0.f;
+#endif /* PLANETARY_SPH */
+#if defined(REMIX_SPH)
+    p->rho = 1.f;
+    p->m0 = 1.f;
+    p->density.rho_dh = 0.f;
+    p->density.wcount = 48.f / (kernel_norm * pow_dimension(p->h));
+    p->density.wcount_dh = 0.f;
+    p->gradient.m0_bar = 1.f;
+    p->gradient.grad_m0_bar_gradhterm = 0.f;
+    memset(p->grad_m0, 0.f, 3 * sizeof(float));
+    memset(p->dv_norm_kernel, 0.f, 3 * 3 * sizeof(float));
+    memset(p->du_norm_kernel, 0.f, 3 * sizeof(float));
+    memset(p->drho_norm_kernel, 0.f, 3 * sizeof(float));
+    memset(p->dh_norm_kernel, 0.f, 3 * sizeof(float));
+    memset(p->gradient.grad_m0_bar, 0.f, 3 * sizeof(float));
+    memset(p->gradient.m1_bar, 0.f, 3 * sizeof(float));
+    memset(p->gradient.grad_m1_bar, 0.f, 3 * 3 * sizeof(float));
+    memset(p->gradient.grad_m1_bar_gradhterm, 0.f, 3 * sizeof(float));
+    p->gradient.m2_bar.xx = 1.f;
+    p->gradient.m2_bar.yy = 1.f;
+    p->gradient.m2_bar.zz = 1.f;
+    p->gradient.m2_bar.xy = 0.f;
+    p->gradient.m2_bar.xz = 0.f;
+    p->gradient.m2_bar.yz = 0.f;
+    zero_sym_matrix(&p->gradient.grad_m2_bar[0]);
+    zero_sym_matrix(&p->gradient.grad_m2_bar[1]);
+    zero_sym_matrix(&p->gradient.grad_m2_bar[2]);
+    zero_sym_matrix(&p->gradient.grad_m2_bar_gradhterm);
+#endif /* REMIX_SPH */
 
     /* And prepare for a round of force tasks. */
     hydro_prepare_force(p, xp, cosmo, hydro_props, pressure_floor, 0., 0.);
diff --git a/tests/testEOS.c b/tests/testEOS.c
index db6fa4fbef8252245c0c38677dfe47a2ea130cca..514a8dc73ecccd4732b7e340440174f8d49e5a7a 100644
--- a/tests/testEOS.c
+++ b/tests/testEOS.c
@@ -48,300 +48,15 @@
 #endif
 
 /**
- * @brief Write a list of densities, energies, and resulting pressures to file
- *  from an equation of state.
+ * @brief Test planetary equations of state.
  *
  *                      WORK IN PROGRESS
  *
- * So far only does the Hubbard & MacFarlane (1980) equations of state.
- *
- * Usage:
- *      $  ./testEOS  (mat_id)  (do_output)
- *
- * Sys args (optional):
- *      mat_id | int | Material ID, see equation_of_state.h for the options.
- *          Default: 201 (= id_HM80_ice).
- *
- *      do_output | int | Set 1 to write the output file of rho, u, P values,
- *          set 0 for no output. Default: 0.
- *
- * Output text file contains:
- *  header
- *  num_rho num_u   mat_id                      # Header values
- *  rho_0   rho_1   rho_2   ...   rho_num_rho   # Array of densities, rho
- *  u_0     u_1     u_2     ...   u_num_u       # Array of energies, u
- *  P_0_0   P_0_1   ...     P_0_num_u           # Array of pressures, P(rho, u)
- *  P_1_0   ...     ...     P_1_num_u
- *  ...     ...     ...     ...
- *  P_num_rho_0     ...     P_num_rho_num_u
- *  c_0_0   c_0_1   ...     c_0_num_u           # Array of sound speeds, c(rho,
- * u)
- *  c_1_0   ...     ...     c_1_num_u
- *  ...     ...     ...     ...
- *  c_num_rho_0     ...     c_num_rho_num_u
- *
- * Note that the values tested extend beyond the range that most EOS are
- * designed for (e.g. outside table limits), to help test the EOS in case of
- * unexpected particle behaviour.
- *
+ *  *
  */
 
 #ifdef EOS_PLANETARY
-int main(int argc, char *argv[]) {
-  float rho, u, log_rho, log_u, P, c;
-  struct unit_system us;
-  struct swift_params *params =
-      (struct swift_params *)malloc(sizeof(struct swift_params));
-  if (params == NULL) error("Error allocating memory for the parameter file.");
-  const struct phys_const *phys_const = 0;  // Unused placeholder
-  const float J_kg_to_erg_g = 1e4;          // Convert J/kg to erg/g
-  char filename[64];
-  // Output table params
-  const int num_rho = 100, num_u = 100;
-  float log_rho_min = logf(1e-4f), log_rho_max = logf(1e3f),  // Densities (cgs)
-      log_u_min = logf(1e4f),
-        log_u_max = logf(1e13f),  // Sp. int. energies (SI)
-      log_rho_step = (log_rho_max - log_rho_min) / (num_rho - 1.f),
-        log_u_step = (log_u_max - log_u_min) / (num_u - 1.f);
-  float A1_rho[num_rho], A1_u[num_u];
-  // Sys args
-  int mat_id_in, do_output;
-  // Default sys args
-  const int mat_id_def = eos_planetary_id_HM80_ice;
-  const int do_output_def = 0;
-
-  // Check the number of system arguments and use defaults if not provided
-  switch (argc) {
-    case 1:
-      // Default both
-      mat_id_in = mat_id_def;
-      do_output = do_output_def;
-      break;
-
-    case 2:
-      // Read mat_id, default do_output
-      mat_id_in = atoi(argv[1]);
-      do_output = do_output_def;
-      break;
-
-    case 3:
-      // Read both
-      mat_id_in = atoi(argv[1]);
-      do_output = atoi(argv[2]);
-      break;
-
-    default:
-      error("Invalid number of system arguments!\n");
-      mat_id_in = mat_id_def;  // Ignored, just here to keep the compiler happy
-      do_output = do_output_def;
-  };
-
-  enum eos_planetary_material_id mat_id =
-      (enum eos_planetary_material_id)mat_id_in;
-
-  /* Greeting message */
-  printf("This is %s\n", package_description());
-
-  // Check material ID
-  const enum eos_planetary_type_id type =
-      (enum eos_planetary_type_id)(mat_id / eos_planetary_type_factor);
-
-  // Select the material base type
-  switch (type) {
-    // Tillotson
-    case eos_planetary_type_Til:
-      switch (mat_id) {
-        case eos_planetary_id_Til_iron:
-          printf("  Tillotson iron \n");
-          break;
-
-        case eos_planetary_id_Til_granite:
-          printf("  Tillotson granite \n");
-          break;
-
-        case eos_planetary_id_Til_water:
-          printf("  Tillotson water \n");
-          break;
-
-        default:
-          error("Unknown material ID! mat_id = %d \n", mat_id);
-      };
-      break;
-
-    // Hubbard & MacFarlane (1980)
-    case eos_planetary_type_HM80:
-      switch (mat_id) {
-        case eos_planetary_id_HM80_HHe:
-          printf("  Hubbard & MacFarlane (1980) hydrogen-helium atmosphere \n");
-          break;
-
-        case eos_planetary_id_HM80_ice:
-          printf("  Hubbard & MacFarlane (1980) ice mix \n");
-          break;
-
-        case eos_planetary_id_HM80_rock:
-          printf("  Hubbard & MacFarlane (1980) rock mix \n");
-          break;
-
-        default:
-          error("Unknown material ID! mat_id = %d \n", mat_id);
-      };
-      break;
-
-    // SESAME
-    case eos_planetary_type_SESAME:
-      switch (mat_id) {
-        case eos_planetary_id_SESAME_iron:
-          printf("  SESAME basalt 7530 \n");
-          break;
-
-        case eos_planetary_id_SESAME_basalt:
-          printf("  SESAME basalt 7530 \n");
-          break;
-
-        case eos_planetary_id_SESAME_water:
-          printf("  SESAME water 7154 \n");
-          break;
-
-        case eos_planetary_id_SS08_water:
-          printf("  Senft & Stewart (2008) SESAME-like water \n");
-          break;
-
-        default:
-          error("Unknown material ID! mat_id = %d \n", mat_id);
-      };
-      break;
-
-    default:
-      error("Unknown material type! mat_id = %d \n", mat_id);
-  }
-
-  // Convert to internal units
-  // Earth masses and radii
-  //  units_init(&us, 5.9724e27, 6.3710e8, 1.f, 1.f, 1.f);
-  // SI
-  units_init(&us, 1000.f, 100.f, 1.f, 1.f, 1.f);
-  log_rho_min -= logf(units_cgs_conversion_factor(&us, UNIT_CONV_DENSITY));
-  log_rho_max -= logf(units_cgs_conversion_factor(&us, UNIT_CONV_DENSITY));
-  log_u_min += logf(J_kg_to_erg_g / units_cgs_conversion_factor(
-                                        &us, UNIT_CONV_ENERGY_PER_UNIT_MASS));
-  log_u_max += logf(J_kg_to_erg_g / units_cgs_conversion_factor(
-                                        &us, UNIT_CONV_ENERGY_PER_UNIT_MASS));
-
-  // Set the input parameters
-  // Which EOS to initialise
-  parser_set_param(params, "EoS:planetary_use_Til:1");
-  parser_set_param(params, "EoS:planetary_use_HM80:1");
-  parser_set_param(params, "EoS:planetary_use_SESAME:1");
-  // Table file names
-  parser_set_param(params,
-                   "EoS:planetary_HM80_HHe_table_file:"
-                   "../examples/planetary_HM80_HHe.txt");
-  parser_set_param(params,
-                   "EoS:planetary_HM80_ice_table_file:"
-                   "../examples/planetary_HM80_ice.txt");
-  parser_set_param(params,
-                   "EoS:planetary_HM80_rock_table_file:"
-                   "../examples/planetary_HM80_rock.txt");
-  parser_set_param(params,
-                   "EoS:planetary_SESAME_iron_table_file:"
-                   "../examples/planetary_SESAME_iron_2140.txt");
-  parser_set_param(params,
-                   "EoS:planetary_SESAME_basalt_table_file:"
-                   "../examples/planetary_SESAME_basalt_7530.txt");
-  parser_set_param(params,
-                   "EoS:planetary_SESAME_water_table_file:"
-                   "../examples/planetary_SESAME_water_7154.txt");
-  parser_set_param(params,
-                   "EoS:planetary_SS08_water_table_file:"
-                   "../examples/planetary_SS08_water.txt");
-
-  // Initialise the EOS materials
-  eos_init(&eos, phys_const, &us, params);
-
-  // Manual debug testing
-  if (1) {
-    printf("\n ### MANUAL DEBUG TESTING ### \n");
-
-    rho = 5960;
-    u = 1.7e8;
-    P = gas_pressure_from_internal_energy(rho, u, eos_planetary_id_HM80_ice);
-    printf("u = %.2e,    rho = %.2e,    P = %.2e \n", u, rho, P);
-
-    return 0;
-  }
-
-  // Output file
-  sprintf(filename, "testEOS_rho_u_P_c_%d.txt", mat_id);
-  FILE *f = fopen(filename, "w");
-  if (f == NULL) {
-    printf("Could not open output file!\n");
-    exit(EXIT_FAILURE);
-  }
-
-  if (do_output == 1) {
-    fprintf(f, "Density  Sp.Int.Energy  mat_id \n");
-    fprintf(f, "%d      %d            %d \n", num_rho, num_u, mat_id);
-  }
-
-  // Densities
-  log_rho = log_rho_min;
-  for (int i = 0; i < num_rho; i++) {
-    A1_rho[i] = exp(log_rho);
-    log_rho += log_rho_step;
-
-    if (do_output == 1)
-      fprintf(f, "%.6e ",
-              A1_rho[i] * units_cgs_conversion_factor(&us, UNIT_CONV_DENSITY));
-  }
-  if (do_output == 1) fprintf(f, "\n");
-
-  // Sp. int. energies
-  log_u = log_u_min;
-  for (int i = 0; i < num_u; i++) {
-    A1_u[i] = exp(log_u);
-    log_u += log_u_step;
-
-    if (do_output == 1)
-      fprintf(f, "%.6e ",
-              A1_u[i] * units_cgs_conversion_factor(
-                            &us, UNIT_CONV_ENERGY_PER_UNIT_MASS));
-  }
-  if (do_output == 1) fprintf(f, "\n");
-
-  // Pressures
-  for (int i = 0; i < num_rho; i++) {
-    rho = A1_rho[i];
-
-    for (int j = 0; j < num_u; j++) {
-      P = gas_pressure_from_internal_energy(rho, A1_u[j], mat_id);
-
-      if (do_output == 1)
-        fprintf(f, "%.6e ",
-                P * units_cgs_conversion_factor(&us, UNIT_CONV_PRESSURE));
-    }
-
-    if (do_output == 1) fprintf(f, "\n");
-  }
-
-  // Sound speeds
-  for (int i = 0; i < num_rho; i++) {
-    rho = A1_rho[i];
-
-    for (int j = 0; j < num_u; j++) {
-      c = gas_soundspeed_from_internal_energy(rho, A1_u[j], mat_id);
-
-      if (do_output == 1)
-        fprintf(f, "%.6e ",
-                c * units_cgs_conversion_factor(&us, UNIT_CONV_SPEED));
-    }
-
-    if (do_output == 1) fprintf(f, "\n");
-  }
-  fclose(f);
-
-  return 0;
-}
+int main(int argc, char *argv[]) { return 0; }
 #else
 int main(int argc, char *argv[]) { return 0; }
 #endif
diff --git a/tests/testEOS.sh b/tests/testEOS.sh
deleted file mode 100755
index bcd87eabbf15a962808843dda76d1829f2917c97..0000000000000000000000000000000000000000
--- a/tests/testEOS.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-
-echo ""
-
-rm -f testEOS_rho_u_P_*.txt
-
-echo "Running testEOS for each planetary material"
-
-A1_mat_id=(
-    100
-    101
-    102
-    200
-    201
-    202
-    300
-    301
-    302
-    303
-)
-
-for mat_id in "${A1_mat_id[@]}"
-do
-    ./testEOS "$mat_id" 1
-done
-
-exit $?
diff --git a/tests/testInteractions.c b/tests/testInteractions.c
index 005c4666b71bc41f8e31f0afa0bd017358eaa9bb..960c2f92543ee278415842d9682e108da5051d70 100644
--- a/tests/testInteractions.c
+++ b/tests/testInteractions.c
@@ -114,7 +114,8 @@ void prepare_force(struct part *parts, size_t count) {
 #if !defined(GIZMO_MFV_SPH) && !defined(MINIMAL_SPH) &&              \
     !defined(PLANETARY_SPH) && !defined(HOPKINS_PU_SPH) &&           \
     !defined(HOPKINS_PU_SPH_MONAGHAN) && !defined(ANARCHY_PU_SPH) && \
-    !defined(SPHENIX_SPH) && !defined(PHANTOM_SPH) && !defined(GASOLINE_SPH)
+    !defined(SPHENIX_SPH) && !defined(PHANTOM_SPH) &&                \
+    !defined(GASOLINE_SPH) && !defined(REMIX_SPH)
   struct part *p;
   for (size_t i = 0; i < count; ++i) {
     p = &parts[i];
diff --git a/tests/testPeriodicBC.c b/tests/testPeriodicBC.c
index 05918b5b5e861a2ffc9342b5248790fd77b3e362..be31b66051e6f5a17902a26b3c069f621731f0f7 100644
--- a/tests/testPeriodicBC.c
+++ b/tests/testPeriodicBC.c
@@ -139,6 +139,9 @@ struct cell *make_cell(size_t n, double *offset, double size, double h,
 #else
         part->mass = density * volume / count;
 #endif
+#if defined(REMIX_SPH)
+        part->rho_evol = density;
+#endif
 
 #if defined(HOPKINS_PE_SPH)
         part->entropy = 1.f;
diff --git a/tests/testRiemannTRRS.c b/tests/testRiemannTRRS.c
index 16956bec0fac0b645a577c8e4869450a21eff6d8..db988c3021bf808b6239fe4cb9a5cafc1ebc0eb0 100644
--- a/tests/testRiemannTRRS.c
+++ b/tests/testRiemannTRRS.c
@@ -18,6 +18,8 @@
  ******************************************************************************/
 #include <config.h>
 
+#if !defined(RIEMANN_SOLVER_NONE)
+
 /* Local headers. */
 #include <string.h>
 
@@ -326,3 +328,7 @@ int main(int argc, char* argv[]) {
 
   return 0;
 }
+
+#else
+int main(int argc, char* argv[]) { return 0; }
+#endif /* !RIEMANN_SOLVER_NONE */