diff --git a/examples/RadiativeTransferTests/Advection_2D/makeIC.py b/examples/RadiativeTransferTests/Advection_2D/makeIC.py
index 923f9a56bf5964d9fa4dcea4854ce62e64a9fc04..e9742cbd6279129c696a6e5eb9230c29868870f4 100755
--- a/examples/RadiativeTransferTests/Advection_2D/makeIC.py
+++ b/examples/RadiativeTransferTests/Advection_2D/makeIC.py
@@ -40,7 +40,7 @@ from swiftsimio import Writer
 # define unit system to use
 unitsystem = unyt.unit_systems.cgs_unit_system
 
-# Box is 1 Mpc
+# define box size
 boxsize = 1e10 * unitsystem["length"]
 
 # number of photon groups
diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/README b/examples/RadiativeTransferTests/CollidingBeams_1D/README
new file mode 100644
index 0000000000000000000000000000000000000000..0aaee5bb32546bc83f47a0bbb1c9c3d9178fabea
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_1D/README
@@ -0,0 +1,13 @@
+Collide two photon packets with each other, and see what happens.
+Two photon groups contain packets using a top hat functions. One of
+them has the lower value at zero, the other is at nonzero. The third
+photon group packets are Gaussians.
+Ideally, they should just pass through each other. But that is not
+what moment based methods do with radiation.
+
+
+To run with GEAR-RT, compile SWIFT with
+
+    --with-rt=GEAR_2 --with-rt-riemann-solver=GLF --with-hydro-dimension=1 --with-hydro=gizmo-mfv \
+     --with-riemann-solver=hllc --with-stars=GEAR --with-feedback=none --with-grackle=$GRACKLE_ROOT
+
diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/makeIC.py b/examples/RadiativeTransferTests/CollidingBeams_1D/makeIC.py
new file mode 100755
index 0000000000000000000000000000000000000000..ecfd7a24df2557f895a6115adffe9a6721f37c51
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_1D/makeIC.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com)
+#               2022 Tsang Keung Chan (chantsangkeung@gmail.com)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+# -----------------------------------------------------------
+# Add initial conditions for photon energies and fluxes
+# for 1D advection of photons.
+# First photon group: Top hat function with zero as the
+#       baseline.
+# Second photon group: Top hat function with nonzero value
+#       as the baseline.
+# Third photon group: Gaussian.
+# -----------------------------------------------------------
+
+import h5py
+import numpy as np
+import unyt
+from swiftsimio import Writer
+
+# define unit system to use
+unitsystem = unyt.unit_systems.cgs_unit_system
+
+# Box is 1 Mpc
+boxsize = 1e10 * unitsystem["length"]
+
+# number of photon groups
+nPhotonGroups = 3
+
+# number of particles in each dimension
+n_p = 1000
+
+# filename of ICs to be generated
+outputfilename = "collision_1D.hdf5"
+
+
+def initial_condition(x):
+    """
+    The initial conditions that will be advected
+
+    x: particle position. 3D unyt array
+
+    returns: 
+    E: photon energy density for each photon group. List of scalars with size of nPhotonGroups
+    F: photon flux for each photon group. List with size of nPhotonGroups of numpy arrays of shape (3,)
+    """
+
+    # you can make the photon quantities unitless, the units will
+    # already have been written down in the writer.
+
+    E_list = []
+    F_list = []
+
+    # Group 1 Photons:
+    # -------------------
+
+    F = np.zeros(3, dtype=np.float64)
+
+    if x[0] < 0.33 * boxsize:
+        E = 1.0
+        F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E
+    elif x[0] < 0.66 * boxsize:
+        E = 0.0
+        F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E
+    else:
+        E = 1.0
+        F[0] = -unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E
+
+    E_list.append(E)
+    F_list.append(F)
+
+    # Group 2 Photons:
+    # -------------------
+
+    F = np.zeros(3, dtype=np.float64)
+    if x[0] < 0.33 * boxsize:
+        E = 3.0
+        F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E
+    elif x[0] < 0.66 * boxsize:
+        E = 1.0
+        if x[0] < 0.5 * boxsize:
+            F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E
+        else:
+            F[0] = -unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E
+    else:
+        E = 3.0
+        F[0] = -unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E
+
+    E_list.append(E)
+    F_list.append(F)
+
+    # Group 3 Photons:
+    # -------------------
+    sigma = 0.05 * boxsize
+    if x[0] < 0.5 * boxsize:
+        mean = 0.33 / 2 * boxsize
+        sign = 1
+    else:
+        mean = (5.0 / 6.0) * boxsize
+        sign = -1
+    amplitude = 2.0
+
+    E = amplitude * np.exp(-(x[0] - mean) ** 2 / (2 * sigma ** 2))
+    F = np.zeros(3, dtype=np.float64)
+    F[0] = sign * unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E
+
+    E_list.append(E)
+    F_list.append(F)
+
+    return E_list, F_list
+
+
+if __name__ == "__main__":
+
+    xp = unyt.unyt_array(np.zeros((n_p, 3), dtype=np.float64), boxsize.units)
+
+    dx = boxsize / n_p
+
+    for i in range(n_p):
+        xp[i, 0] = (i + 0.5) * dx
+
+    w = Writer(unyt.unit_systems.cgs_unit_system, boxsize, dimension=1)
+
+    w.gas.coordinates = xp
+    w.gas.velocities = np.zeros(xp.shape) * (unyt.cm / unyt.s)
+    w.gas.masses = np.ones(xp.shape[0], dtype=np.float64) * 1000 * unyt.g
+    w.gas.internal_energy = (
+        np.ones(xp.shape[0], dtype=np.float64) * (300.0 * unyt.kb * unyt.K) / unyt.g
+    )
+
+    # Generate initial guess for smoothing lengths based on MIPS
+    w.gas.generate_smoothing_lengths(boxsize=boxsize, dimension=1)
+
+    # If IDs are not present, this automatically generates
+    w.write(outputfilename)
+
+    # Now open file back up again and add photon groups
+    # you can make them unitless, the units have already been
+    # written down in the writer. In this case, it's in cgs.
+
+    F = h5py.File(outputfilename, "r+")
+    header = F["Header"]
+    nparts = header.attrs["NumPart_ThisFile"][0]
+    parts = F["/PartType0"]
+
+    for grp in range(nPhotonGroups):
+        dsetname = "PhotonEnergiesGroup{0:d}".format(grp + 1)
+        energydata = np.zeros((nparts), dtype=np.float32)
+        parts.create_dataset(dsetname, data=energydata)
+
+        dsetname = "PhotonFluxesGroup{0:d}".format(grp + 1)
+        #  if dsetname not in parts.keys():
+        fluxdata = np.zeros((nparts, 3), dtype=np.float32)
+        parts.create_dataset(dsetname, data=fluxdata)
+
+    for p in range(nparts):
+        E, Flux = initial_condition(xp[p])
+        for g in range(nPhotonGroups):
+            Esetname = "PhotonEnergiesGroup{0:d}".format(g + 1)
+            parts[Esetname][p] = E[g]
+            Fsetname = "PhotonFluxesGroup{0:d}".format(g + 1)
+            parts[Fsetname][p] = Flux[g]
+
+    #  from matplotlib import pyplot as plt
+    #  plt.figure()
+    #  for g in range(nPhotonGroups):
+    #      Esetname = "PhotonEnergiesGroup{0:d}".format(g+1)
+    #      plt.plot(xp[:,0], parts[Esetname], label="E "+str(g+1))
+    #      #  Fsetname = "PhotonFluxesGroup{0:d}".format(g+1)
+    #      #  plt.plot(xp[:,0], parts[Fsetname][:,0], label="F "+str(g+1))
+    #  plt.legend()
+    #  plt.show()
+
+    F.close()
diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/plotEnergies.py b/examples/RadiativeTransferTests/CollidingBeams_1D/plotEnergies.py
new file mode 100755
index 0000000000000000000000000000000000000000..4d5285467455e775e225a02a05378060070599c0
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_1D/plotEnergies.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+# ----------------------------------------------
+# Plot the total photon energies in each photon
+# group over the course of several snapshots
+# ----------------------------------------------
+
+import os
+import sys
+
+import matplotlib as mpl
+import numpy as np
+import swiftsimio
+from matplotlib import pyplot as plt
+
+# Parameters users should/may tweak
+snapshot_base = "output"  # snapshot basename
+
+
+# -----------------------------------------------------------------------
+
+mpl.rcParams["text.usetex"] = True
+
+# Read in cmdline arg: Are we plotting only one snapshot, or all?
+plot_all = False  # plot all snapshots
+try:
+    snapnr = int(sys.argv[1])
+except IndexError:
+    plot_all = True
+
+
+def get_snapshot_list(snapshot_basename="output"):
+    """
+    Find the snapshot(s) that are to be plotted 
+    and return their names as list """
+
+    snaplist = []
+
+    if plot_all:
+        dirlist = os.listdir()
+        for f in dirlist:
+            if f.startswith(snapshot_basename) and f.endswith("hdf5"):
+                snaplist.append(f)
+
+        snaplist = sorted(snaplist)
+
+    else:
+        fname = snapshot_basename + "_" + str(snapnr).zfill(4) + ".hdf5"
+        if not os.path.exists(fname):
+            print("Didn't find file", fname)
+            quit(1)
+        snaplist.append(fname)
+
+    return snaplist
+
+
+def plot_energies(snap_nrs, energy_sums):
+    """
+    Create the actual plot.
+    """
+
+    # Plot plot plot!
+    fig = plt.figure(figsize=(5.0, 5.4), dpi=200)
+    figname = "energy_budget.png"
+
+    ax1 = fig.add_subplot(1, 1, 1)
+
+    ngroups = energy_sums.shape[1]
+
+    for g in range(ngroups):
+        ax1.plot(snap_nrs, energy_sums[:, g], label=None)
+        ax1.scatter(snap_nrs, energy_sums[:, g], label="group {0:d}".format(g + 1))
+
+    ax1.set_xlabel("Snapshot")
+    ax1.set_ylabel(
+        r"Total energy [$" + energy_sums.units.latex_representation() + "$]",
+        usetex=True,
+    )
+
+    # add title
+    title = "Energy Budget"
+    ax1.set_title(title)
+    ax1.legend()
+
+    plt.tight_layout()
+    plt.savefig(figname)
+    plt.close()
+
+    return
+
+
+def get_photon_energies(snaplist):
+    """
+    Get total photon energy in each photon group for a list of
+    snapshots specified by `snaplist`
+
+    snaplist: list of snapshot filenames
+
+    returns:
+
+        snap_nrs : list of integers of snapshot numbers
+        energy_sums: np.array(shape=(len snaplist, ngroups)) of 
+            total photon energies per group per snapshot
+    """
+
+    data = swiftsimio.load(snaplist[0])
+    meta = data.metadata
+    ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"])
+
+    energy_sums = np.zeros((len(snaplist), ngroups))
+    snap_nrs = np.zeros(len(snaplist), dtype=int)
+
+    for f, filename in enumerate(snaplist):
+
+        data = swiftsimio.load(filename)
+
+        for g in range(ngroups):
+            en = getattr(data.gas.photon_energies, "group" + str(g + 1))
+            energy_sums[f, g] = en.sum()
+
+        nrstring = filename[len(snapshot_base) + 1 : -len(".hdf5")]
+        nr = int(nrstring)
+        snap_nrs[f] = nr
+
+    energy_sums = energy_sums * en.units
+
+    sortind = np.argsort(snap_nrs)
+    snap_nrs = snap_nrs[sortind]
+    energy_sums = energy_sums[sortind]
+
+    return snap_nrs, energy_sums
+
+
+if __name__ == "__main__":
+
+    snaplist = get_snapshot_list(snapshot_base)
+    snap_nrs, energy_sums = get_photon_energies(snaplist)
+
+    plot_energies(snap_nrs, energy_sums)
diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/plotSolution.py b/examples/RadiativeTransferTests/CollidingBeams_1D/plotSolution.py
new file mode 100755
index 0000000000000000000000000000000000000000..5e372d67976336086dac3579a524790242d238e8
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_1D/plotSolution.py
@@ -0,0 +1,264 @@
+#!/usr/bin/env python3
+
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+# ----------------------------------------------
+# plot photon data assuming a 1D problem
+# give snapshot number as cmdline arg to plot
+# single snapshot, otherwise this script plots
+# all snapshots available in the workdir
+# ----------------------------------------------
+
+import os
+import sys
+
+import matplotlib as mpl
+import numpy as np
+import swiftsimio
+from matplotlib import pyplot as plt
+
+# Parameters users should/may tweak
+snapshot_base = "output"  # snapshot basename
+
+# properties for all scatterplots
+scatterplot_kwargs = {
+    "facecolor": "red",
+    "s": 4,
+    "alpha": 0.6,
+    "linewidth": 0.0,
+    "marker": ".",
+}
+
+# -----------------------------------------------------------------------
+
+mpl.rcParams["text.usetex"] = True
+
+# Read in cmdline arg: Are we plotting only one snapshot, or all?
+plot_all = False  # plot all snapshots
+try:
+    snapnr = int(sys.argv[1])
+except IndexError:
+    plot_all = True
+
+
+def get_snapshot_list(snapshot_basename="output"):
+    """
+    Find the snapshot(s) that are to be plotted 
+    and return their names as list
+    """
+
+    snaplist = []
+
+    if plot_all:
+        dirlist = os.listdir()
+        for f in dirlist:
+            if f.startswith(snapshot_basename) and f.endswith("hdf5"):
+                snaplist.append(f)
+
+        snaplist = sorted(snaplist)
+
+    else:
+        fname = snapshot_basename + "_" + str(snapnr).zfill(4) + ".hdf5"
+        if not os.path.exists(fname):
+            print("Didn't find file", fname)
+            quit(1)
+        snaplist.append(fname)
+
+    return snaplist
+
+
+def plot_photons(filename, energy_boundaries=None, flux_boundaries=None):
+    """
+    Create the actual plot.
+
+    filename: file to work with
+    energy_boundaries:  list of [E_min, E_max] for each photon group. 
+                        If none, limits are set automatically.
+    flux_boundaries:    list of [F_min, F_max] for each photon group. 
+                        If none, limits are set automatically.
+    """
+
+    print("working on", filename)
+
+    # Read in data firt
+    data = swiftsimio.load(filename)
+    meta = data.metadata
+    scheme = str(meta.subgrid_scheme["RT Scheme"].decode("utf-8"))
+
+    ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"])
+
+    for g in range(ngroups):
+        # workaround to access named columns data with swiftsimio visualisaiton
+        new_attribute_str = "radiation_energy" + str(g + 1)
+        en = getattr(data.gas.photon_energies, "group" + str(g + 1))
+        setattr(data.gas, new_attribute_str, en)
+
+        # prepare also the fluxes
+        for direction in ["X"]:
+            new_attribute_str = "radiation_flux" + str(g + 1) + direction
+            f = getattr(data.gas.photon_fluxes, "Group" + str(g + 1) + direction)
+            setattr(data.gas, new_attribute_str, f)
+
+    part_positions = data.gas.coordinates[:, 0].copy()
+
+    # Plot plot plot!
+    fig = plt.figure(figsize=(5.0 * ngroups, 5.4), dpi=200)
+    figname = filename[:-5] + "-all-quantities.png"
+
+    for g in range(ngroups):
+
+        # plot energy
+        new_attribute_str = "radiation_energy" + str(g + 1)
+        photon_energy = getattr(data.gas, new_attribute_str)
+
+        ax = fig.add_subplot(2, ngroups, g + 1)
+
+        ax.scatter(
+            part_positions, photon_energy, **scatterplot_kwargs, label="simulation"
+        )
+        ax.legend()
+
+        ax.set_title("Group {0:2d}".format(g + 1))
+        if g == 0:
+            ax.set_ylabel(
+                "Energies [$" + photon_energy.units.latex_representation() + "$]"
+            )
+        ax.set_xlabel("x [$" + part_positions.units.latex_representation() + "$]")
+
+        if energy_boundaries is not None:
+
+            if abs(energy_boundaries[g][1]) > abs(energy_boundaries[g][0]):
+                fixed_min = energy_boundaries[g][0] - 0.1 * abs(energy_boundaries[g][1])
+                fixed_max = energy_boundaries[g][1] * 1.1
+            else:
+                fixed_min = energy_boundaries[g][0] * 1.1
+                fixed_max = energy_boundaries[g][1] + 0.1 * abs(energy_boundaries[g][0])
+            ax.set_ylim(fixed_min, fixed_max)
+
+        # plot flux X
+        new_attribute_str = "radiation_flux" + str(g + 1) + "X"
+        photon_flux = getattr(data.gas, new_attribute_str)
+        if scheme.startswith("GEAR M1closure"):
+            photon_flux = photon_flux.to("erg/cm**2/s")
+        elif scheme.startswith("SPH M1closure"):
+            photon_flux = photon_flux.to("erg*cm/s")
+        else:
+            print("Error: Unknown RT scheme " + scheme)
+            exit()
+
+        ax = fig.add_subplot(2, ngroups, g + 1 + ngroups)
+        ax.scatter(part_positions, photon_flux, **scatterplot_kwargs)
+
+        if g == 0:
+            ax.set_ylabel("Flux X [$" + photon_flux.units.latex_representation() + "$]")
+        ax.set_xlabel("x [$" + part_positions.units.latex_representation() + "$]")
+
+        if flux_boundaries is not None:
+
+            if abs(flux_boundaries[g][1]) > abs(flux_boundaries[g][0]):
+                fixed_min = flux_boundaries[g][0] - 0.1 * abs(flux_boundaries[g][1])
+                fixed_max = flux_boundaries[g][1] * 1.1
+            else:
+                fixed_min = flux_boundaries[g][0] * 1.1
+                fixed_max = flux_boundaries[g][1] + 0.1 * abs(flux_boundaries[g][0])
+
+            ax.set_ylim(fixed_min, fixed_max)
+
+    # add title
+    title = filename.replace("_", r"\_")  # exception handle underscore for latex
+    if meta.cosmology is not None:
+        title += ", $z$ = {0:.2e}".format(meta.z)
+    title += ", $t$ = {0:.2e}".format(meta.time)
+    fig.suptitle(title)
+
+    plt.tight_layout()
+    plt.savefig(figname)
+    plt.close()
+
+    return
+
+
+def get_minmax_vals(snaplist):
+    """
+    Find minimal and maximal values for energy and flux,
+    so you can fix axes limits over all snapshots
+
+    snaplist: list of snapshot filenames
+
+    returns:
+
+    energy_boundaries: list of [E_min, E_max] for each photon group
+    flux_boundaries: list of [Fx_min, Fy_max] for each photon group
+    """
+
+    emins = []
+    emaxs = []
+    fmins = []
+    fmaxs = []
+
+    for filename in snaplist:
+
+        data = swiftsimio.load(filename)
+        meta = data.metadata
+
+        ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"])
+        emin_group = []
+        emax_group = []
+        fluxmin_group = []
+        fluxmax_group = []
+
+        for g in range(ngroups):
+            en = getattr(data.gas.photon_energies, "group" + str(g + 1))
+            emin_group.append(en.min())
+            emax_group.append(en.max())
+
+            for direction in ["X"]:
+                f = getattr(data.gas.photon_fluxes, "Group" + str(g + 1) + direction)
+                fluxmin_group.append(f.min())
+                fluxmax_group.append(f.max())
+
+        emins.append(emin_group)
+        emaxs.append(emax_group)
+        fmins.append(fluxmin_group)
+        fmaxs.append(fluxmax_group)
+
+    energy_boundaries = []
+    flux_boundaries = []
+    for g in range(ngroups):
+        emin = min([emins[f][g] for f in range(len(snaplist))])
+        emax = max([emaxs[f][g] for f in range(len(snaplist))])
+        energy_boundaries.append([emin, emax])
+        fmin = min([fmins[f][g] for f in range(len(snaplist))])
+        fmax = max([fmaxs[f][g] for f in range(len(snaplist))])
+        flux_boundaries.append([fmin, fmax])
+
+    return energy_boundaries, flux_boundaries
+
+
+if __name__ == "__main__":
+
+    snaplist = get_snapshot_list(snapshot_base)
+    energy_boundaries, flux_boundaries = get_minmax_vals(snaplist)
+
+    for f in snaplist:
+        plot_photons(
+            f, energy_boundaries=energy_boundaries, flux_boundaries=flux_boundaries
+        )
diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/rt_collision1D.yml b/examples/RadiativeTransferTests/CollidingBeams_1D/rt_collision1D.yml
new file mode 100644
index 0000000000000000000000000000000000000000..25f24418dada5071893d5b8bc1679dfd77c4b614
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_1D/rt_collision1D.yml
@@ -0,0 +1,62 @@
+MetaData:
+  run_name: RT_collision-1D
+
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.
+  UnitLength_in_cgs:   1.
+  UnitVelocity_in_cgs: 1.
+  UnitCurrent_in_cgs:  1.
+  UnitTemp_in_cgs:     1.
+
+# Parameters governing the time integration
+TimeIntegration:
+  max_nr_rt_subcycles: 1
+  time_begin: 0.    # The starting time of the simulation (in internal units).
+  time_end:   4.e-1  # The end time of the simulation (in internal units).
+  dt_min:     1.e-8 # The minimal time-step size of the simulation (in internal units).
+  dt_max:     1.e-02  # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the snapshots
+Snapshots:
+  basename:            output # Common part of the name of output files
+  time_first:          0.    # Time of the first output (in internal units)
+  delta_time:          2.e-2
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  time_first:          0.
+  delta_time:          4.e-2 # Time between statistics output
+
+# Parameters for the hydrodynamics scheme
+SPH:
+  resolution_eta:        1.2348   # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel).
+  CFL_condition:         0.6      # Courant-Friedrich-Levy condition for time integration.
+  minimal_temperature:   10.      # Kelvin
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  ./collision_1D.hdf5  # The file to read
+  periodic:   1                     # periodic ICs
+
+Restarts:
+  delta_hours:        72        # (Optional) decimal hours between dumps of restart files.
+
+GEARRT:
+  f_reduce_c: 1.                  # reduce the speed of light for the RT solver by multiplying c with this factor
+  f_limit_cooling_time: 0.0       # (Optional) multiply the cooling time by this factor when estimating maximal next time step. Set to 0.0 to turn computation of cooling time off.
+  CFL_condition: 0.8              # CFL condition for RT, independent of hydro
+  photon_groups_Hz: [0., 1., 2.]  # Lower photon frequency group bin edges in Hz. Needs to have exactly N elements, where N is the configured number of bins --with-RT=GEAR_N
+  stellar_luminosity_model: const                # Which model to use to determine the stellar luminosities.
+  const_stellar_luminosities_LSol: [0., 0., 0.]  # (Conditional) constant star luminosities for each photon frequency group to use if stellar_luminosity_model:const is set, in units of Solar Luminosity.
+  hydrogen_mass_fraction:  0.76                  # total hydrogen (H + H+) mass fraction in the metal-free portion of the gas
+  stellar_spectrum_type: 0                       # Which radiation spectrum to use. 0: constant from 0 until some max frequency set by stellar_spectrum_const_max_frequency_Hz. 1: blackbody spectrum.
+  stellar_spectrum_const_max_frequency_Hz: 1.e17 # (Conditional) if stellar_spectrum_type=0, use this maximal frequency for the constant spectrum. 
+  skip_thermochemistry: 1                        # ignore thermochemistry.
+  set_initial_ionization_mass_fractions: 1       # (Optional) manually overwrite initial mass fraction of each species (using the values you set below)
+  mass_fraction_HI: 0.76                         # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HI mass fractions to this value
+  mass_fraction_HII: 0.                          # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HII mass fractions to this value
+  mass_fraction_HeI: 0.24                        # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeI mass fractions to this value
+  mass_fraction_HeII: 0.                         # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeII mass fractions to this value
+  mass_fraction_HeIII: 0.                        # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeIII mass fractions to this value
+
diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/run.sh b/examples/RadiativeTransferTests/CollidingBeams_1D/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..1d121bcc7eeefbb6e257057f78cae61e1981cf58
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_1D/run.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# make run.sh fail if a subcommand fails
+set -e
+set -o pipefail
+
+if [ ! -f collision_1D.hdf5 ]; then
+    echo "Generating ICs"
+    python3 makeIC.py
+fi
+
+# Run SWIFT with RT
+../../../swift \
+    --hydro \
+    --threads=1 \
+    --verbose=0  \
+    --radiation \
+    --stars \
+    --feedback \
+    --external-gravity \
+    ./rt_collision1D.yml 2>&1 | tee output.log
+
+python3 ./plotEnergies.py
+python3 ./plotSolution.py
diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/README b/examples/RadiativeTransferTests/CollidingBeams_2D/README
new file mode 100644
index 0000000000000000000000000000000000000000..7bc95ce3b54b0281536b7c1fea3bbbfce267e4e9
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_2D/README
@@ -0,0 +1,10 @@
+Collide packets of radiation against each other, and see how the M1
+closure treats them.
+
+Collide them horizontally, vertically, diagonally, and from the side.
+
+To run with GEAR-RT, configure SWIFT using
+
+    --with-rt=GEAR_4 --with-rt-riemann-solver=GLF --with-hydro-dimension=2 --with-hydro=gizmo-mfv \
+     --with-riemann-solver=hllc --with-stars=GEAR --with-feedback=none --with-grackle=$GRACKLE_ROOT
+
diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/getGlass.sh b/examples/RadiativeTransferTests/CollidingBeams_2D/getGlass.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ae3c977064f5e7a408aa249c5fd9089b3c52ecb1
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_2D/getGlass.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/glassPlane_128.hdf5
diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/makeIC.py b/examples/RadiativeTransferTests/CollidingBeams_2D/makeIC.py
new file mode 100755
index 0000000000000000000000000000000000000000..be2d50a7bb2a6503195a0d6c700f3ee6615091de
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_2D/makeIC.py
@@ -0,0 +1,302 @@
+#!/usr/bin/env python3
+
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+# -------------------------------------------------------------
+# Add initial conditions for photon energies and fluxes
+# for 2D advection of photons.
+# First photon group: Top hat function with zero as the
+#       baseline, advects in x direction
+# Second photon group: Top hat function with nonzero value
+#       as the baseline, advcts in y direction.
+# Third photon group: Gaussian advecting diagonally
+# Fourth photon group: Circle moving radially from the center
+# -------------------------------------------------------------
+
+import h5py
+import numpy as np
+import unyt
+from swiftsimio import Writer
+
+# define unit system to use
+unitsystem = unyt.unit_systems.cgs_unit_system
+
+# define box size
+boxsize = 1e10 * unitsystem["length"]
+
+# number of photon groups
+nPhotonGroups = 4
+
+# filename of ICs to be generated
+outputfilename = "collision_2D.hdf5"
+
+
+def get_radiation_IC(pos, numPart, ngroups):
+
+    Elist = []
+    Flist = []
+
+    x = pos[:, 0]
+    y = pos[:, 1]
+    c = unyt.c.to(unitsystem["length"] / unitsystem["time"])
+
+    for g in range(ngroups):
+
+        E = np.zeros(numPart)
+        F = np.zeros((numPart, 3))
+
+        # prepare particle array masks
+        flux_sign = np.ones(numPart, dtype=float)
+
+        if g == 0:
+            # horizontal beams
+            # -----------------------
+
+            left = np.logical_and(x > 0.0, x < 1.0 / 3)
+            right = np.logical_and(x > 2.0 / 3, x < 1.0)
+            xcriterion = np.logical_or(left, right)
+            ycriterion = np.logical_and(y > 0.4, y < 0.6)
+            is_max = np.logical_and(xcriterion, ycriterion)
+
+            flux_sign[right] = -1.0
+
+            Emax = 1.0
+            E[is_max] = Emax
+            F[is_max, 0] = Emax * c * flux_sign[is_max]
+
+        if g == 1:
+            # vertical beams, nonzero energy everywhere
+            # -------------------------------------------
+
+            top = np.logical_and(y > 2.0 / 3.0, y < 1.0)
+            bottom = np.logical_and(y > 0.0, y < 1.0 / 3.0)
+
+            xcriterion = np.logical_and(x > 0.4, x < 0.6)
+            ycriterion = np.logical_or(top, bottom)
+            is_max = np.logical_and(xcriterion, ycriterion)
+
+            flux_sign[y > 0.5] = -1.0
+
+            Emax = 2.0
+            Emin = 1.0
+            E[:] = Emin
+            E[is_max] = Emax
+            F[:, 1] = E * c * flux_sign
+
+        if g == 2:
+            # diagonal beams
+            # -------------------------------------------
+
+            width = 0.1
+            l = np.sqrt(2) / 3.0
+            sin = np.sin(np.pi / 4)
+            xprime = l * sin
+            x0 = xprime - 0.5 * width * sin
+            x1 = xprime + 0.5 * width * sin
+            x2 = 1 - xprime - 0.5 * width * sin
+            x3 = 1 - xprime + 0.5 * width * sin
+
+            def upper_line(x):
+                return x + 0.5 * width
+
+            def lower_line(x):
+                return x - 0.5 * width
+
+            def descending_left(x, xprime):
+                return -(x - xprime) + xprime
+
+            def descending_right(x, xprime):
+                return -(x - 1.0 + xprime) + 1.0 - xprime
+
+            first = x < x0
+            lower_first = np.logical_and(y < upper_line(x), y > lower_line(x))
+            firstcond = np.logical_and(first, lower_first)
+
+            second = np.logical_and(x > x0, x < x1)
+            lower_second = np.logical_and(
+                y > lower_line(x), y < descending_left(x, xprime)
+            )
+            secondcond = np.logical_and(second, lower_second)
+
+            third = np.logical_and(x > x2, x < x3)
+            upper_third = np.logical_and(
+                y < upper_line(x), y > descending_right(x, xprime)
+            )
+            thirdcond = np.logical_and(third, upper_third)
+
+            fourth = np.logical_and(x > x3, x < 1.0)
+            upper_fourth = np.logical_and(y < upper_line(x), y > lower_line(x))
+            fourthcond = np.logical_and(fourth, upper_fourth)
+
+            Emax = 1.0
+            E[firstcond] = Emax
+            E[secondcond] = Emax
+            E[thirdcond] = Emax
+            E[fourthcond] = Emax
+
+            flux_sign[thirdcond] = -1.0
+            flux_sign[fourthcond] = -1.0
+
+            F[:, 0] = E * c * flux_sign / np.sqrt(2)
+            F[:, 1] = E * c * flux_sign / np.sqrt(2)
+
+        if g == 3:
+            # diagonal beams that meed in the middle
+            # -------------------------------------------
+
+            width = 0.1
+            l = np.sqrt(2) / 3.0
+            sin = np.sin(np.pi / 4)
+            xprime = l * sin
+            x0 = xprime - 0.5 * width * sin
+            x1 = xprime + 0.5 * width * sin
+            x2 = 1 - xprime - 0.5 * width * sin
+            x3 = 1 - xprime + 0.5 * width * sin
+
+            def upper_line(x):
+                return x + 0.5 * width
+
+            def lower_line(x):
+                return x - 0.5 * width
+
+            def descending_left(x, xprime):
+                return -(x - xprime) + xprime
+
+            def descending_lower(x, xprime):
+                return -(x - 1 + xprime) + xprime - 0.5 * width
+
+            def descending_upper(x, xprime):
+                return -(x - 1.0 + xprime) + xprime + 0.5 * width
+
+            def ascending_right(x, xprime):
+                return (x - 1 + xprime) + xprime
+
+            first = x < x0
+            lower_first = np.logical_and(y < upper_line(x), y > lower_line(x))
+            firstcond = np.logical_and(first, lower_first)
+
+            second = np.logical_and(x > x0, x < x1)
+            lower_second = np.logical_and(
+                y > lower_line(x), y < descending_left(x, xprime)
+            )
+            secondcond = np.logical_and(second, lower_second)
+
+            third = np.logical_and(x > x2, x < x3)
+            upper_third = np.logical_and(
+                y < ascending_right(x, xprime), y > descending_lower(x, xprime)
+            )
+            thirdcond = np.logical_and(third, upper_third)
+
+            fourth = np.logical_and(x > x3, x < 1.0)
+            upper_fourth = np.logical_and(
+                y < descending_upper(x, xprime), y > descending_lower(x, xprime)
+            )
+            fourthcond = np.logical_and(fourth, upper_fourth)
+
+            Emax = 1.0
+            E[firstcond] = Emax
+            E[secondcond] = Emax
+            E[thirdcond] = Emax
+            E[fourthcond] = Emax
+
+            flux_sign[thirdcond] = -1.0
+            flux_sign[fourthcond] = -1.0
+
+            F[:, 0] = E * c * flux_sign / np.sqrt(2)
+            F[:, 1] = E * c / np.sqrt(2)
+
+        #  histE, _, _ = np.histogram2d(pos[:,0], pos[:,1], weights=E, bins=50)
+        #  histFx, _, _ = np.histogram2d(pos[:,0], pos[:,1], weights=F[:,0], bins=50)
+        #  histFy, _, _ = np.histogram2d(pos[:,0], pos[:,1], weights=F[:,1], bins=50)
+        #  from matplotlib import pyplot as plt
+        #
+        #  fig = plt.figure()
+        #  ax1 = fig.add_subplot(1, 3, 1)
+        #  ax1.imshow(histE.T, origin="lower")
+        #  ax2 = fig.add_subplot(1, 3, 2)
+        #  ax2.imshow(histFx.T, origin="lower")
+        #  ax3 = fig.add_subplot(1, 3, 3)
+        #  ax3.imshow(histFy.T, origin="lower")
+        #  plt.show()
+
+        Elist.append(E)
+        Flist.append(F)
+
+    return Elist, Flist
+
+
+if __name__ == "__main__":
+    glass = h5py.File("glassPlane_128.hdf5", "r")
+
+    # Read particle positions and h from the glass
+    pos = glass["/PartType0/Coordinates"][:, :]
+    h = glass["/PartType0/SmoothingLength"][:]
+    glass.close()
+
+    numPart = np.size(h)
+
+    # get radiation IC while particle coordinates are still [0, 1)
+    Elist, Flist = get_radiation_IC(pos, numPart, nPhotonGroups)
+
+    pos *= boxsize
+    h *= boxsize
+
+    w = Writer(unyt.unit_systems.cgs_unit_system, boxsize, dimension=2)
+
+    w.gas.coordinates = pos
+    w.gas.velocities = np.zeros((numPart, 3)) * (unyt.cm / unyt.s)
+    w.gas.masses = np.ones(numPart, dtype=np.float64) * 1000 * unyt.g
+    w.gas.internal_energy = (
+        np.ones(numPart, dtype=np.float64) * (300.0 * unyt.kb * unyt.K) / unyt.g
+    )
+
+    # Generate initial guess for smoothing lengths based on MIPS
+    w.gas.smoothing_length = h
+
+    # If IDs are not present, this automatically generates
+    w.write(outputfilename)
+
+    # Now open file back up again and add photon groups
+    # you can make them unitless, the units have already been
+    # written down in the writer. In this case, it's in cgs.
+
+    F = h5py.File(outputfilename, "r+")
+    header = F["Header"]
+    nparts = header.attrs["NumPart_ThisFile"][0]
+    parts = F["/PartType0"]
+
+    for grp in range(nPhotonGroups):
+        dsetname = "PhotonEnergiesGroup{0:d}".format(grp + 1)
+        energydata = np.zeros(nparts, dtype=np.float32)
+        parts.create_dataset(dsetname, data=energydata)
+
+        dsetname = "PhotonFluxesGroup{0:d}".format(grp + 1)
+        #  if dsetname not in parts.keys():
+        fluxdata = np.zeros((nparts, 3), dtype=np.float32)
+        parts.create_dataset(dsetname, data=fluxdata)
+
+    for g in range(nPhotonGroups):
+        Esetname = "PhotonEnergiesGroup{0:d}".format(g + 1)
+        parts[Esetname][:] = Elist[g][:]
+        Fsetname = "PhotonFluxesGroup{0:d}".format(g + 1)
+        parts[Fsetname][:, :] = Flist[g][:, :]
+
+    F.close()
diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/plotEnergies.py b/examples/RadiativeTransferTests/CollidingBeams_2D/plotEnergies.py
new file mode 100755
index 0000000000000000000000000000000000000000..4d5285467455e775e225a02a05378060070599c0
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_2D/plotEnergies.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+# ----------------------------------------------
+# Plot the total photon energies in each photon
+# group over the course of several snapshots
+# ----------------------------------------------
+
+import os
+import sys
+
+import matplotlib as mpl
+import numpy as np
+import swiftsimio
+from matplotlib import pyplot as plt
+
+# Parameters users should/may tweak
+snapshot_base = "output"  # snapshot basename
+
+
+# -----------------------------------------------------------------------
+
+mpl.rcParams["text.usetex"] = True
+
+# Read in cmdline arg: Are we plotting only one snapshot, or all?
+plot_all = False  # plot all snapshots
+try:
+    snapnr = int(sys.argv[1])
+except IndexError:
+    plot_all = True
+
+
+def get_snapshot_list(snapshot_basename="output"):
+    """
+    Find the snapshot(s) that are to be plotted 
+    and return their names as list """
+
+    snaplist = []
+
+    if plot_all:
+        dirlist = os.listdir()
+        for f in dirlist:
+            if f.startswith(snapshot_basename) and f.endswith("hdf5"):
+                snaplist.append(f)
+
+        snaplist = sorted(snaplist)
+
+    else:
+        fname = snapshot_basename + "_" + str(snapnr).zfill(4) + ".hdf5"
+        if not os.path.exists(fname):
+            print("Didn't find file", fname)
+            quit(1)
+        snaplist.append(fname)
+
+    return snaplist
+
+
+def plot_energies(snap_nrs, energy_sums):
+    """
+    Create the actual plot.
+    """
+
+    # Plot plot plot!
+    fig = plt.figure(figsize=(5.0, 5.4), dpi=200)
+    figname = "energy_budget.png"
+
+    ax1 = fig.add_subplot(1, 1, 1)
+
+    ngroups = energy_sums.shape[1]
+
+    for g in range(ngroups):
+        ax1.plot(snap_nrs, energy_sums[:, g], label=None)
+        ax1.scatter(snap_nrs, energy_sums[:, g], label="group {0:d}".format(g + 1))
+
+    ax1.set_xlabel("Snapshot")
+    ax1.set_ylabel(
+        r"Total energy [$" + energy_sums.units.latex_representation() + "$]",
+        usetex=True,
+    )
+
+    # add title
+    title = "Energy Budget"
+    ax1.set_title(title)
+    ax1.legend()
+
+    plt.tight_layout()
+    plt.savefig(figname)
+    plt.close()
+
+    return
+
+
+def get_photon_energies(snaplist):
+    """
+    Get total photon energy in each photon group for a list of
+    snapshots specified by `snaplist`
+
+    snaplist: list of snapshot filenames
+
+    returns:
+
+        snap_nrs : list of integers of snapshot numbers
+        energy_sums: np.array(shape=(len snaplist, ngroups)) of 
+            total photon energies per group per snapshot
+    """
+
+    data = swiftsimio.load(snaplist[0])
+    meta = data.metadata
+    ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"])
+
+    energy_sums = np.zeros((len(snaplist), ngroups))
+    snap_nrs = np.zeros(len(snaplist), dtype=int)
+
+    for f, filename in enumerate(snaplist):
+
+        data = swiftsimio.load(filename)
+
+        for g in range(ngroups):
+            en = getattr(data.gas.photon_energies, "group" + str(g + 1))
+            energy_sums[f, g] = en.sum()
+
+        nrstring = filename[len(snapshot_base) + 1 : -len(".hdf5")]
+        nr = int(nrstring)
+        snap_nrs[f] = nr
+
+    energy_sums = energy_sums * en.units
+
+    sortind = np.argsort(snap_nrs)
+    snap_nrs = snap_nrs[sortind]
+    energy_sums = energy_sums[sortind]
+
+    return snap_nrs, energy_sums
+
+
+if __name__ == "__main__":
+
+    snaplist = get_snapshot_list(snapshot_base)
+    snap_nrs, energy_sums = get_photon_energies(snaplist)
+
+    plot_energies(snap_nrs, energy_sums)
diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/plotSolution.py b/examples/RadiativeTransferTests/CollidingBeams_2D/plotSolution.py
new file mode 100755
index 0000000000000000000000000000000000000000..8f100e5fae99c15fb18d63b3f572db6f045d802f
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_2D/plotSolution.py
@@ -0,0 +1,310 @@
+#!/usr/bin/env python3
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+# ----------------------------------------------------
+# plot photon data for 2D problems
+# give snapshot number as cmdline arg to plot
+# single snapshot, otherwise this script plots
+# all snapshots available in the workdir
+# ----------------------------------------------------
+
+import gc
+import os
+import sys
+
+import matplotlib as mpl
+import swiftsimio
+from matplotlib import pyplot as plt
+from mpl_toolkits.axes_grid1 import make_axes_locatable
+
+# Parameters users should/may tweak
+plot_all_data = False  # plot all groups and all photon quantities
+snapshot_base = "output"  # snapshot basename
+fancy = True  # fancy up the plots a bit?
+
+# parameters for imshow plots
+imshow_kwargs = {"origin": "lower", "cmap": "viridis"}
+
+
+projection_kwargs = {"resolution": 1024, "parallel": True}
+# -----------------------------------------------------------------------
+
+
+# Read in cmdline arg: Are we plotting only one snapshot, or all?
+plot_all = False
+try:
+    snapnr = int(sys.argv[1])
+except IndexError:
+    plot_all = True
+
+mpl.rcParams["text.usetex"] = True
+
+
+def get_snapshot_list(snapshot_basename="output"):
+    """
+    Find the snapshot(s) that are to be plotted 
+    and return their names as list
+    """
+
+    snaplist = []
+
+    if plot_all:
+        dirlist = os.listdir()
+        for f in dirlist:
+            if f.startswith(snapshot_basename) and f.endswith("hdf5"):
+                snaplist.append(f)
+
+        snaplist = sorted(snaplist)
+
+    else:
+        fname = snapshot_basename + "_" + str(snapnr).zfill(4) + ".hdf5"
+        if not os.path.exists(fname):
+            print("Didn't find file", fname)
+            quit(1)
+        snaplist.append(fname)
+
+    return snaplist
+
+
+def set_colorbar(ax, im):
+    divider = make_axes_locatable(ax)
+    cax = divider.append_axes("right", size="5%", pad=0.05)
+    plt.colorbar(im, cax=cax)
+    return
+
+
+def plot_photons(filename, energy_boundaries=None, flux_boundaries=None):
+    """
+    Create the actual plot.
+
+    filename: file to work with
+    energy_boundaries:  list of [E_min, E_max] for each photon group. 
+                        If none, limits are set automatically.
+    flux_boundaries:    list of [F_min, F_max] for each photon group. 
+                        If none, limits are set automatically.
+    """
+
+    print("working on", filename)
+
+    # Read in data first
+    data = swiftsimio.load(filename)
+    meta = data.metadata
+
+    ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"])
+    xlabel_units_str = meta.boxsize.units.latex_representation()
+
+    global imshow_kwargs
+    imshow_kwargs["extent"] = [0, meta.boxsize[0].v, 0, meta.boxsize[1].v]
+
+    for g in range(ngroups):
+        # workaround to access named columns data with swiftsimio visualisaiton
+        # add mass weights to remove surface density dependence in images
+        new_attribute_str = "mass_weighted_radiation_energy" + str(g + 1)
+        en = getattr(data.gas.photon_energies, "group" + str(g + 1))
+        en = en * data.gas.masses
+        setattr(data.gas, new_attribute_str, en)
+
+        if plot_all_data:
+            # prepare also the fluxes
+            #  for direction in ["X", "Y", "Z"]:
+            for direction in ["X", "Y"]:
+                new_attribute_str = (
+                    "mass_weighted_radiation_flux" + str(g + 1) + direction
+                )
+                f = getattr(data.gas.photon_fluxes, "Group" + str(g + 1) + direction)
+                f *= data.gas.masses
+                setattr(data.gas, new_attribute_str, f)
+
+    # get mass surface density projection that we'll use to remove density dependence in image
+    mass_map = swiftsimio.visualisation.projection.project_gas(
+        data, project="masses", **projection_kwargs
+    )
+
+    if plot_all_data:
+        fig = plt.figure(figsize=(5 * 3, 5.05 * ngroups), dpi=200)
+        figname = filename[:-5] + "-all-quantities.png"
+
+        for g in range(ngroups):
+
+            # get energy projection
+            new_attribute_str = "mass_weighted_radiation_energy" + str(g + 1)
+            photon_map = swiftsimio.visualisation.projection.project_gas(
+                data, project=new_attribute_str, **projection_kwargs
+            )
+            photon_map = photon_map / mass_map
+
+            ax = fig.add_subplot(ngroups, 3, g * 3 + 1)
+            if energy_boundaries is not None:
+                imshow_kwargs["vmin"] = energy_boundaries[g][0]
+                imshow_kwargs["vmax"] = energy_boundaries[g][1]
+            im = ax.imshow(photon_map.T, **imshow_kwargs)
+            set_colorbar(ax, im)
+            ax.set_ylabel("Group {0:2d}".format(g + 1))
+            ax.set_xlabel("x [$" + xlabel_units_str + "$]")
+            if g == 0:
+                ax.set_title("Energies")
+
+            # get flux X projection
+            new_attribute_str = "mass_weighted_radiation_flux" + str(g + 1) + "X"
+            photon_map = swiftsimio.visualisation.projection.project_gas(
+                data, project=new_attribute_str, **projection_kwargs
+            )
+            photon_map = photon_map / mass_map
+
+            ax = fig.add_subplot(ngroups, 3, g * 3 + 2)
+            if flux_boundaries is not None:
+                imshow_kwargs["vmin"] = flux_boundaries[g][0]
+                imshow_kwargs["vmax"] = flux_boundaries[g][1]
+            im = ax.imshow(photon_map.T, **imshow_kwargs)
+            set_colorbar(ax, im)
+            ax.set_xlabel("x [$" + xlabel_units_str + "$]")
+            ax.set_ylabel("y [$" + xlabel_units_str + "$]")
+            if g == 0:
+                ax.set_title("Flux X")
+
+            # get flux Y projection
+            new_attribute_str = "mass_weighted_radiation_flux" + str(g + 1) + "Y"
+            photon_map = swiftsimio.visualisation.projection.project_gas(
+                data, project=new_attribute_str, **projection_kwargs
+            )
+            photon_map = photon_map / mass_map
+
+            ax = fig.add_subplot(ngroups, 3, g * 3 + 3)
+            im = ax.imshow(photon_map.T, **imshow_kwargs)
+            set_colorbar(ax, im)
+            ax.set_xlabel("x [$" + xlabel_units_str + "$]")
+            ax.set_ylabel("y [$" + xlabel_units_str + "$]")
+            if g == 0:
+                ax.set_title("Flux Y")
+
+    else:  # plot just energies
+
+        fig = plt.figure(figsize=(5 * ngroups, 5), dpi=200)
+        figname = filename[:-5] + ".png"
+
+        for g in range(ngroups):
+
+            # get projection
+            new_attribute_str = "mass_weighted_radiation_energy" + str(g + 1)
+            photon_map = swiftsimio.visualisation.projection.project_gas(
+                data, project=new_attribute_str, **projection_kwargs
+            )
+            photon_map = photon_map / mass_map
+
+            ax = fig.add_subplot(1, ngroups, g + 1)
+            if energy_boundaries is not None:
+                imshow_kwargs["vmin"] = energy_boundaries[g][0]
+                imshow_kwargs["vmax"] = energy_boundaries[g][1]
+            im = ax.imshow(photon_map.T, **imshow_kwargs)
+            set_colorbar(ax, im)
+            ax.set_title("Group {0:2d}".format(g + 1))
+            if g == 0:
+                ax.set_ylabel("Energies")
+
+    # Add title
+    title = filename.replace("_", r"\_")  # exception handle underscore for latex
+    if meta.cosmology is not None:
+        title += ", $z$ = {0:.2e}".format(meta.z)
+    title += ", $t$ = {0:.2e}".format(meta.time)
+    fig.suptitle(title)
+
+    plt.tight_layout()
+    plt.savefig(figname)
+    plt.close()
+    gc.collect()
+
+    return
+
+
+def get_minmax_vals(snaplist):
+    """
+    Find minimal and maximal values for energy and flux,
+    so you can fix axes limits over all snapshots
+
+    snaplist: list of snapshot filenames
+
+    returns:
+
+    energy_boundaries: list of [E_min, E_max] for each photon group
+    flux_boundaries: list of [Fx_min, Fy_max] for each photon group
+    """
+
+    emins = []
+    emaxs = []
+    fmins = []
+    fmaxs = []
+
+    for filename in snaplist:
+
+        data = swiftsimio.load(filename)
+        meta = data.metadata
+
+        ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"])
+        emin_group = []
+        emax_group = []
+        fluxmin_group = []
+        fluxmax_group = []
+
+        for g in range(ngroups):
+            en = getattr(data.gas.photon_energies, "group" + str(g + 1))
+            emin_group.append(en.min())
+            emax_group.append(en.max())
+
+            dirmin = []
+            dirmax = []
+            for direction in ["X", "Y"]:
+                f = getattr(data.gas.photon_fluxes, "Group" + str(g + 1) + direction)
+                dirmin.append(f.min())
+                dirmax.append(f.max())
+            fluxmin_group.append(min(dirmin))
+            fluxmax_group.append(max(dirmax))
+
+        emins.append(emin_group)
+        emaxs.append(emax_group)
+        fmins.append(fluxmin_group)
+        fmaxs.append(fluxmax_group)
+
+    energy_boundaries = []
+    flux_boundaries = []
+    for g in range(ngroups):
+        emin = min([emins[f][g] for f in range(len(snaplist))])
+        emax = max([emaxs[f][g] for f in range(len(snaplist))])
+        energy_boundaries.append([emin, emax])
+        fmin = min([fmins[f][g] for f in range(len(snaplist))])
+        fmax = max([fmaxs[f][g] for f in range(len(snaplist))])
+        flux_boundaries.append([fmin, fmax])
+
+    return energy_boundaries, flux_boundaries
+
+
+if __name__ == "__main__":
+
+    snaplist = get_snapshot_list(snapshot_base)
+    if fancy:
+        energy_boundaries, flux_boundaries = get_minmax_vals(snaplist)
+    else:
+        energy_boundaries = None
+        flux_boundaries = None
+
+    for f in snaplist:
+        plot_photons(
+            f, energy_boundaries=energy_boundaries, flux_boundaries=flux_boundaries
+        )
diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/rt_collision2D.yml b/examples/RadiativeTransferTests/CollidingBeams_2D/rt_collision2D.yml
new file mode 100644
index 0000000000000000000000000000000000000000..44daff0259e0bd8165b8d18beb13afe459bb040c
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_2D/rt_collision2D.yml
@@ -0,0 +1,60 @@
+MetaData:
+  run_name: RT_collision-2D
+
+# Define the system of units to use internally. 
+InternalUnitSystem:
+  UnitMass_in_cgs:     1.
+  UnitLength_in_cgs:   1.
+  UnitVelocity_in_cgs: 1.
+  UnitCurrent_in_cgs:  1.
+  UnitTemp_in_cgs:     1.
+
+# Parameters governing the time integration
+TimeIntegration:
+  max_nr_rt_subcycles: 1
+  time_begin: 0.     # The starting time of the simulation (in internal units).
+  time_end:   3.4e-1 # The end time of the simulation (in internal units).
+  dt_min:     1.e-08 # The minimal time-step size of the simulation (in internal units).
+  dt_max:     1.e-02 # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the snapshots
+Snapshots:
+  basename:            output # Common part of the name of output files
+  time_first:          0.    # Time of the first output (in internal units)
+  delta_time:          0.017
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+  time_first:          0.
+  delta_time:          0.034 # Time between statistics output
+
+# Parameters for the hydrodynamics scheme
+SPH:
+  resolution_eta:        1.2348   # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel).
+  CFL_condition:         0.6      # Courant-Friedrich-Levy condition for time integration.
+  minimal_temperature:   10.      # Kelvin
+
+# Parameters related to the initial conditions
+InitialConditions:
+  file_name:  ./collision_2D.hdf5  # The file to read
+  periodic:   1                    # periodic ICs
+
+GEARRT:
+  f_reduce_c: 1.           
+  # reduce the speed of light for the RT solver by multiplying c with this factor
+  f_limit_cooling_time: 0.0                       # (Optional) multiply the cooling time by this factor when estimating maximal next time step. Set to 0.0 to turn computation of cooling time off.
+  CFL_condition: 0.8                              # CFL condition for RT, independent of hydro
+  photon_groups_Hz: [1., 2., 3., 4.]              # Lower photon frequency group bin edges in Hz. Needs to have exactly N elements, where N is the configured number of bins --with-RT=GEAR_N
+  stellar_luminosity_model: const                 # Which model to use to determine the stellar luminosities.
+  const_stellar_luminosities_LSol: [0., 0., 0., 0.]   # (Conditional) constant star luminosities for each photon frequency group to use if stellar_luminosity_model:const is set, in units of Solar Luminosity.
+  hydrogen_mass_fraction:  0.76                   # total hydrogen (H + H+) mass fraction in the metal-free portion of the gas
+  stellar_spectrum_type: 0                        # Which radiation spectrum to use. 0: constant from 0 until some max frequency set by stellar_spectrum_const_max_frequency_Hz. 1: blackbody spectrum.
+  stellar_spectrum_const_max_frequency_Hz: 1.e17  # (Conditional) if stellar_spectrum_type=0, use this maximal frequency for the constant spectrum. 
+  set_initial_ionization_mass_fractions: 1        # (Optional) manually overwrite initial mass fraction of each species (using the values you set below)
+  mass_fraction_HI: 0.76                          # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HI mass fractions to this value
+  mass_fraction_HII: 0.                           # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HII mass fractions to this value
+  mass_fraction_HeI: 0.24                         # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeI mass fractions to this value
+  mass_fraction_HeII: 0.                          # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeII mass fractions to this value
+  mass_fraction_HeIII: 0.                         # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeIII mass fractions to this value
+  skip_thermochemistry: 1                         # Skip thermochemsitry.
+
diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/run.sh b/examples/RadiativeTransferTests/CollidingBeams_2D/run.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e7af6a0ec80635dddb58397754590ca8854a732c
--- /dev/null
+++ b/examples/RadiativeTransferTests/CollidingBeams_2D/run.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+# exit if anything fails
+set -e
+set -o pipefail
+
+ # Generate the initial conditions if they are not present.
+if [ ! -e glassPlane_128.hdf5 ]
+then
+    echo "Fetching initial glass file for the 2D RT advection example..."
+    ./getGlass.sh
+fi
+if [ ! -e collision_2D.hdf5 ]
+then
+    echo "Generating initial conditions for the 2D RT advection example..."
+    python3 makeIC.py
+fi
+
+# Run SWIFT with RT
+../../../swift \
+    --hydro \
+    --threads=4 \
+    --verbose=0  \
+    --radiation \
+    --stars \
+    --feedback \
+    --external-gravity \
+    ./rt_collision2D.yml 2>&1 | tee output.log
+
+python3 ./plotEnergies.py
+python3 ./plotSolution.py