diff --git a/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/config.cfg b/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/config.cfg
new file mode 100755
index 0000000000000000000000000000000000000000..067b942df5494321fd7d5440df1b7dff32b4f11a
--- /dev/null
+++ b/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/config.cfg
@@ -0,0 +1,2 @@
+[pack]
+files = main.cu Makefile
\ No newline at end of file
diff --git a/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/include/DiffusionSpace_sparseGrid.hpp b/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/include/DiffusionSpace_sparseGrid.hpp
new file mode 100755
index 0000000000000000000000000000000000000000..d613e0a43d3d205f0cf966f20052d1dd8d3b10d9
--- /dev/null
+++ b/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/include/DiffusionSpace_sparseGrid.hpp
@@ -0,0 +1,295 @@
+//
+// Created by jstark on 28.09.21.
+//
+
+#ifndef SPARSEGRID_DIFFUSIONSPACE_SPARSEGRID_HPP
+#define SPARSEGRID_DIFFUSIONSPACE_SPARSEGRID_HPP
+
+#include "math.h"
+
+template <typename T>
+static bool is_diffusionSpace(const T & phi_sdf, const T & lower_bound, const T & upper_bound)
+{
+	const T EPSILON = std::numeric_limits<T>::epsilon();
+	const T _b_low = lower_bound + EPSILON;
+	const T _b_up  = upper_bound  - EPSILON;
+	return (phi_sdf > _b_low && phi_sdf < _b_up);
+}
+
+template <size_t PHI_SDF_ECS_FULL, size_t PHI_SDF_ECS_SPARSE, typename grid_type, typename sparse_in_type, typename T>
+void get_diffusion_domain_sparse_grid(grid_type & grid,
+									  sparse_in_type & sparse_grid,
+									  const T b_low,
+									  const T b_up)
+{
+	auto dom = grid.getDomainIterator();
+	while(dom.isNext())
+	{
+		auto key = dom.get();
+		if(is_diffusionSpace(grid.template get<PHI_SDF_ECS_FULL>(key), b_low, b_up))
+		{
+			sparse_grid.template insertFlush<PHI_SDF_ECS_SPARSE>(key) = grid.template get<PHI_SDF_ECS_FULL>(key);
+		}
+		++dom;
+	}
+	// Mapping?
+	// Load Balancing?
+}
+
+#if 0
+template <size_t PHI_SDF_ECS_FULL, size_t PHI_SDF_ECS_SPARSE, typename grid_type, typename grid_type_upscale, typename sparse_in_type, typename T>
+void get_diffusion_domain_sparse_grid_upscale(grid_type & grid,
+											  grid_type_upscale & grid_upscale,
+									          sparse_in_type & sparse_grid,
+									          const int upscale_factor, // total upscale factor that is product all dimensions, e.g. 2^3 = 8
+									          const T b_low,
+									          const T b_up)
+{
+	auto dom = grid.getDomainIterator();
+	auto dom_upscale = grid_upscale.getDomainIterator();
+	while(dom.isNext())
+	{
+		auto key = dom.get();
+		auto key_upscale = dom_upscale.get();
+
+		for(int i = 0, i < upscale_factor, ++i) // Place upscale number of points for each key
+		{
+			if(is_diffusionSpace(grid.template get<PHI_SDF_ECS_FULL>(key), b_low, b_up))
+			{
+				sparse_grid.template insertFlush<PHI_SDF_ECS_SPARSE>(key_upscale) = grid.template get<PHI_SDF_ECS_FULL>(key);
+			}
+			++dom_upscale
+		}
+
+
+		++dom;
+	}
+
+}
+#endif
+
+template <
+size_t PHI_SDF_ECS_FULL,
+size_t PHI_SDF_SHELL_FULL,
+size_t PHI_SDF_ECS_SPARSE,
+size_t PHI_SDF_SHELL_SPARSE,
+typename grid_type, 
+typename sparse_in_type, 
+typename T>
+void get_diffusion_domain_sparse_grid_with_shell(grid_type & grid_ecs,
+									  grid_type & grid_shell,
+									  sparse_in_type & sparse_grid,
+									  const T b_low,
+									  const T b_up)
+{
+	auto dom = grid_ecs.getDomainIterator();
+	while(dom.isNext())
+	{
+		auto key = dom.get();
+		if(is_diffusionSpace(grid_ecs.template get<PHI_SDF_ECS_FULL>(key), b_low, b_up))
+		{
+			sparse_grid.template insertFlush<PHI_SDF_ECS_SPARSE>(key)   = grid_ecs.template get<PHI_SDF_ECS_FULL>(key);
+			sparse_grid.template insertFlush<PHI_SDF_SHELL_SPARSE>(key) = grid_shell.template get<PHI_SDF_SHELL_FULL>(key);
+		}
+		++dom;
+	}
+}
+
+
+#if 0
+// Init c0
+	auto dom = g_sparse.getDomainIterator();
+	while(dom.isNext())
+	{
+		auto key = dom.get();
+		if (g_sparse.template getProp<PHI_SDF>(key) < 4 * g_sparse.spacing(x))
+		{
+			g_sparse.template getProp<CONC_N>(key) =
+			        (1 - 0.25 * g_sparse.template getProp<PHI_SDF>(key)) / g_sparse.spacing(x);
+		}
+		++dom;
+	}
+	if(v_cl.rank() == 0) std::cout << "Initialized grid with smoothed c0 at the boundary." << std::endl;
+	g_sparse.write(path_output + "g_init", FORMAT_BINARY);
+#endif
+
+
+
+
+template <typename point_type, typename y_margin_type>
+auto distance_from_margin(point_type & coord, y_margin_type y_margin)
+{
+	return(coord.get(1) - y_margin);
+}
+
+template <typename point_type, typename y_margin_type, typename width_type>
+bool is_source(point_type & coord, y_margin_type y_margin, width_type width_source)
+{
+	return (coord.get(1) >= y_margin
+			&& distance_from_margin(coord, y_margin) <= width_source);
+}
+
+template <typename T>
+static bool is_inner_surface(const T & phi_sdf, const T & lower_bound)
+{
+	const T EPSILON = std::numeric_limits<T>::epsilon();
+	const T _b_low = lower_bound + EPSILON;
+	return (phi_sdf > _b_low);
+}
+
+template <size_t PHI_SDF, size_t K_SOURCE, size_t K_SINK, typename grid_type, typename y_margin_type, typename width_type, typename k_type>
+void init_reactionTerms(grid_type & grid,
+                        width_type width_source,
+						y_margin_type y_margin,
+                        k_type k_source,
+                        k_type k_sink,
+                        int no_membrane_points=4)
+{
+	/*
+	 * Setting the reaction terms according to their distance from the margin of the blastoderm along the dorsal-ventral axis
+	 *
+	 * */
+	
+	auto dom = grid.getDomainIterator();
+	while(dom.isNext())
+	{
+		auto key = dom.get();
+		Point<grid_type::dims, y_margin_type> coord = grid.getPos(key);
+		
+		if(grid.template get<PHI_SDF>(key) < no_membrane_points * grid.spacing(0)) // If point lies close to the cell surface
+		{
+			if(is_source(coord, y_margin, width_source)) // If point lies within width_source distance from the margin, set k_source
+			{
+				grid.template insertFlush<K_SOURCE>(key) = k_source;
+			}
+			else
+			{
+				grid.template insertFlush<K_SOURCE>(key) = 0.0; // If membrane point not in source, emission is zero
+			}
+			grid.template insertFlush<K_SINK>(key) = k_sink; // Have sink at all membranes
+			
+		}
+		
+		else // For the nodes that are further away from the membrane, set both reaction terms to zero
+		{
+			grid.template insertFlush<K_SOURCE>(key) = 0.0;
+			grid.template insertFlush<K_SINK>(key) = 0.0;
+		}
+		++dom;
+	}
+}
+
+template <
+size_t PHI_SDF_ECS_SPARSE, 
+size_t PHI_SDF_SHELL_SPARSE, 
+size_t K_SOURCE, 
+size_t K_SINK, 
+typename grid_type, 
+typename y_margin_type, 
+typename width_type, 
+typename k_type,
+typename boundary_type>
+void init_reactionTerms_with_shell(grid_type & grid,
+                        width_type width_source,
+						y_margin_type y_margin,
+                        k_type k_source,
+                        k_type k_sink,
+                        boundary_type b_low_shell,
+                        int no_membrane_points=4)
+{
+	/*
+	 * Setting the reaction terms according to their distance from the margin of the blastoderm along the dorsal-ventral axis
+	 *
+	 * */
+	
+	auto dom = grid.getDomainIterator();
+	while(dom.isNext())
+	{
+		auto key = dom.get();
+		Point<grid_type::dims, y_margin_type> coord = grid.getPos(key);
+		
+		if(
+		grid.template get<PHI_SDF_ECS_SPARSE>(key) < no_membrane_points * grid.spacing(0) // If point is a surface point
+		&& is_inner_surface(grid.template get<PHI_SDF_SHELL_SPARSE>(key), b_low_shell) // and this surface is not towards the yolk or EVL
+		) 
+		{
+			if(is_source(coord, y_margin, width_source)) // If point lies within width_source distance from the margin, set k_source
+			{
+				grid.template insertFlush<K_SOURCE>(key) = k_source;
+			}
+			else
+			{
+				grid.template insertFlush<K_SOURCE>(key) = 0.0; // If membrane point not in source, emission is zero
+			}
+			grid.template insertFlush<K_SINK>(key) = k_sink; // Have sink at all membranes
+			
+		}
+		
+		else // For the nodes that are further away from the membrane or belong to the outer surface, set both reaction terms to zero
+		{
+			grid.template insertFlush<K_SOURCE>(key) = 0.0;
+			grid.template insertFlush<K_SINK>(key) = 0.0;
+		}
+		++dom;
+	}
+}
+
+
+template <size_t PHI_SDF, size_t K_SOURCE, size_t K_SINK, typename grid_type, typename coord_type, typename k_type>
+void init_reactionTerms_smoothed(grid_type & grid,
+                        coord_type y_start,
+                        coord_type y_peak,
+                        coord_type y_stop,
+                        k_type k_source,
+                        k_type k_sink,
+						coord_type no_membrane_points=4,
+						coord_type smoothing=0.25)
+{
+	/*
+	 * Setting the reaction terms according to the position along the animal-vegetal axis = y-axis
+	 *
+	 * */
+	const int x = 0;
+	const int y = 1;
+	const int z = 2;
+	
+	auto dom = grid.getDomainIterator();
+	while(dom.isNext())
+	{
+		auto key = dom.get();
+		Point<grid_type::dims, coord_type> coord = grid.getPos(key);
+//		k_type sdf_based_smoothing = (1 - smoothing * grid.template getProp<PHI_SDF>(key)) / grid.spacing(x);
+		k_type sdf_based_smoothing = 1.0;
+		if(grid.template get<PHI_SDF>(key) < no_membrane_points * grid.spacing(0)) // If grid node lies close to the
+			// membrane
+		{
+			// If membrane on animal side of the peak, emission strength increases with y
+			if (coord[y] > y_start
+			&& coord[y] < y_peak + std::numeric_limits<coord_type>::epsilon())
+			{
+				grid.template insertFlush<K_SOURCE>(key) = k_source * (coord[y] - y_start) * sdf_based_smoothing;
+			}
+			// Else if membrane on vegetal side of the peak, emission strength decreases with y as moving towards the
+			// boundary to the yolk
+			else if (coord[y] >= y_peak - std::numeric_limits<coord_type>::epsilon()
+			&& coord[y] < y_stop - std::numeric_limits<coord_type>::epsilon())
+			{
+				grid.template insertFlush<K_SOURCE>(key) = k_source * (y_stop - coord[y]) * sdf_based_smoothing;
+			}
+			// Else source is zero
+			else grid.template insertFlush<K_SOURCE>(key) = 0.0;
+			grid.template insertFlush<K_SINK>(key) = k_sink; // Have sink at all membranes
+		}
+		
+		else // For the nodes that are further away from the membrane, set the reaction terms to zero
+		{
+			grid.template insertFlush<K_SOURCE>(key) = 0.0;
+			grid.template insertFlush<K_SINK>(key) = 0.0;
+		}
+		++dom;
+	}
+}
+
+
+
+#endif //SPARSEGRID_DIFFUSIONSPACE_SPARSEGRID_HPP
diff --git a/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/include/HelpFunctions_diffusion.hpp b/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/include/HelpFunctions_diffusion.hpp
new file mode 100755
index 0000000000000000000000000000000000000000..7e3069c8549f142ae2f3b31a730533d196530365
--- /dev/null
+++ b/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/include/HelpFunctions_diffusion.hpp
@@ -0,0 +1,195 @@
+//
+// Created by jstark on 08.10.21
+//
+
+#ifndef DIFFUSION_HELPFUNCTIONSDIFFUSION_HPP
+#define DIFFUSION_HELPFUNCTIONSDIFFUSION_HPP
+#include "util/PathsAndFiles.hpp"
+
+#include "level_set/redistancing_Sussman/HelpFunctionsForGrid.hpp"
+
+/**@brief Get timestep that fulfills stability criterion nD-diffusion using a FTCS scheme
+ *
+ * @tparam grid_type Template type of input g_sparse.
+ * @tparam T Template type of diffusion coefficient and timestep.
+ * @param grid Input grid of type grid_type.
+ * @param k_max Max. diffusion coefficient in the domain.
+ * @return T type timestep.
+ */
+template <typename grid_type, typename T>
+T diffusion_time_step(grid_type & grid, T k_max)
+{
+	T sum = 0;
+	for(int d = 0; d < grid_type::dims; ++d)
+	{
+		sum += (T)1.0 / (T)(grid.spacing(d) * grid.spacing(d));
+	}
+	
+	return 1.0 / ((T)4.0 * k_max * sum);
+}
+
+/**@brief Sigmoidal which can be shifted and stretched in order to get desired smooth inhomog. diffusion-coeff scalar
+ * field
+ *
+ * @tparam T Template type of coordinates.
+ * @param x T type abscissa axis of sigmoidal function.
+ * @param x_shift T type shift of sigmoidal along abscissa.
+ * @param x_stretch T type stretching of sigmoidal along acscissa - the smaller, the steeper!
+ * @param y_min T type asymptotic lowest y-value.
+ * @param y_max T type asymptotic maximal y-value.
+ * @return Function value of sigmoidal for input value x.
+ */
+template <typename T>
+T get_smooth_sigmoidal(T x, T x_shift, T x_stretch, T y_min, T y_max)
+{
+	return y_min + y_max / (1.0 + exp(- (x_shift + x_stretch * x)));
+}
+
+/**@brief Sums up property values over all particles in grid.
+ *
+ * @tparam Prop Index of property whose values will be summed up over all particles.
+ * @tparam grid_type Template type of input particle vector.
+ * @param grid Input particle vector. Can be a subset.
+ * @return Sum of values stored in Prop.
+ */
+template <size_t Prop, typename grid_type>
+auto sum_prop_over_grid(grid_type & grid)
+{
+	auto sum = 0.0;
+	auto dom = grid.getDomainIterator();
+	while(dom.isNext())
+	{
+		auto key = dom.get();
+		sum += grid.template getProp<Prop>(key);
+		++dom;
+	}
+	auto &v_cl = create_vcluster();
+	v_cl.sum(sum);
+	v_cl.execute();
+	return sum;
+}
+
+/**@brief Writing out the total mass to a csv file.
+ *
+ * @tparam Conc Index of property containing the concentration.
+ * @tparam T_mass Template type of initial mass.
+ * @param grid Input particle vector_dist of type grid_type.
+ * @param m_initial Initial total mass.
+ * @param t Current time of the diffusion.
+ * @param i Current iteration of the diffusion.
+ * @param path_output std::string Path where csv file will be saved.
+ * @param filename std::string Name of csv file. Default: "/total_mass.csv"
+ * @Outfile 4 columns: Time, Total mass, Mass difference, Max mass
+ */
+template <size_t Conc, typename grid_type, typename T, typename T_mass>
+void monitor_total_mass(grid_type & grid, const T_mass m_initial, const T p_volume, const T t, const size_t i,
+                           const std::string & path_output, const std::string & filename="total_mass.csv")
+{
+	auto &v_cl = create_vcluster();
+	
+//	if (m_initial == 0)
+//	{
+//		if (v_cl.rank() == 0) std::cout << "m_initial is zero! Normalizing the total mass with the initial mass will "
+//										   "not work. Aborting..." << std::endl;
+//		abort();
+//	}
+	
+	T m_total = sum_prop_over_grid<Conc>(grid) * p_volume;
+	T m_diff = m_total - m_initial;
+	T m_max = get_max_val<Conc>(grid) * p_volume;
+	
+	if (v_cl.rank() == 0)
+	{
+		std::string outpath = path_output + "/" + filename;
+		create_file_if_not_exist(outpath);
+		std::ofstream outfile;
+		outfile.open(outpath, std::ios_base::app); // append instead of overwrite
+		
+		if (i == 0)
+		{
+			outfile   << "Time, Total mass, Mass difference, Max mass" << std::endl;
+//			std::cout << "Time, Total mass, Total mass in-/decrease, Max mass" << std::endl;
+		}
+		
+		outfile
+				<< to_string_with_precision(t, 6) << ","
+				<< to_string_with_precision(m_total, 6) << ","
+				<< to_string_with_precision(m_diff, 6) << ","
+				<< to_string_with_precision(m_max, 6)
+				<< std::endl;
+		outfile.close();
+#if 0
+		std::cout
+				<< to_string_with_precision(t, 6) << ","
+				<< to_string_with_precision(m_total, 6) << ","
+				<< to_string_with_precision(m_diff, 6) << ","
+				<< to_string_with_precision(m_max, 6)
+				<< std::endl;
+#endif
+//		if (m_domain_normalized > 100 || m_domain_normalized < 0)
+//		{
+//			std::cout << "Mass increases or is negative, something went wrong. Aborting..." << std::endl;
+//			abort();
+//		}
+	}
+}
+
+/**@brief Writing out the total concentration over time to a csv file.
+ *
+ * @tparam Conc Index of property containing the concentration.
+ * @param grid Input particle vector_dist of type grid_type.
+ * @param t Current time of the diffusion.
+ * @param i Current iteration of the diffusion.
+ * @param path_output std::string Path where csv file will be saved.
+ * @param filename std::string Name of csv file. Default: "/normalized_total_mass.csv"
+ * @Outfile 3 columns: timestep of writeout i, total concentration
+ */
+template <size_t Conc, typename grid_type, typename T>
+void monitor_total_concentration(grid_type & grid, const T t, const size_t i,
+                        const std::string & path_output, const std::string & filename="total_conc.csv")
+{
+	auto &v_cl = create_vcluster();
+	T c_total = sum_prop_over_grid<Conc>(grid);
+
+	if (v_cl.rank() == 0)
+	{
+		std::string outpath = path_output + "/" + filename;
+		create_file_if_not_exist(outpath);
+		std::ofstream outfile;
+		outfile.open(outpath, std::ios_base::app); // append instead of overwrite
+
+		if (i == 0)
+		{
+			outfile   << "Time, Total concentration" << std::endl;
+		}
+
+		outfile
+				<< to_string_with_precision(t, 6) << ","
+				<< to_string_with_precision(c_total, 6)
+				<< std::endl;
+		outfile.close();
+	}
+}
+
+/**@brief Sets the emission term to 0 if concentration surpasses a threshold. This is to avoid accumulation of mass
+ * at unconnected islands.
+ *
+ * @tparam Conc Index of property containing the concentration.
+ * @tparam Emission Index of property containing the emission constant.
+ * @tparam grid_type Template type of input particle vector.
+ * @tparam key_vector_type Template type of particle keys.
+ * @param grid Input particle vector. Can be a subset.
+ * @param keys Keys for which necessity for adaptation is checked.
+ * @param threshold Maximal concentration that is tolerated for keeping emission.
+ */
+template <size_t Conc, size_t Emission, typename grid_type, typename key_vector_type, typename T>
+void adapt_emission(grid_type & grid, key_vector_type & keys, T threshold)
+{
+	for (int i = 0; i < keys.size(); ++i)
+	{
+		auto key = keys.get(i);
+		if (grid.template getProp<Conc>(key) > threshold) grid.template getProp<Emission>(key) = 0.0;
+	}
+}
+
+#endif //DIFFUSION_HELPFUNCTIONSDIFFUSION_HPP
diff --git a/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/main.cu b/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/main.cu
new file mode 100644
index 0000000000000000000000000000000000000000..96bc781fd46b8c42ce93b6a0a56be8659ca837f3
--- /dev/null
+++ b/example/SparseGrid/10_heat_conduction_reticulate_porous_ceramics/main.cu
@@ -0,0 +1,386 @@
+//
+// Created by jstark on 2023-05-05.
+//
+/*!
+ *
+ * \page 10_heat_conduction_RPC Heat conduction in reticulate porous ceramics using sparse grids on GPU
+ *
+ * [TOC]
+ *
+ * # Solving heat conduction in the image-based geometry of reticulate porous ceramics # {#e10_heat_conduction_RPC_gpu}
+ *
+ * In this example, we simulate heat conduction in the solid phase of reticulate porous ceramics with heat dissipation at the surface. For the complete image-based simulation pipeline and performance of this simulation, we refer to
+ * <a href="https://arxiv.org/abs/2304.11165">J. Stark, I. F. Sbalzarini "An open-source pipeline for solving continuous reaction-diffusion models in image-based geometries of porous media." arXiv preprint arXiv:2304.11165 (2023).</a>
+ * The geometry of the solid phase is reconstructed based on $\upmu$CT images provided by kindly provided by Prof. Jörg Petrasch (Michigan State University, College of Engineering) (see: <a href="https://doi.org/10.1111/j.1551-2916.2008.02308.x">J. Petrasch et al., "Tomography-based multiscale analyses of the 3D geometrical morphology of reticulated porous ceramics", Journal of the American Ceramic Society (2008)</a>
+ * and <a href="https://doi.org/10.1115/1.4000226">S. Haussener et al., "Tomography-based heat and mass transfer characterization of reticu- late porous ceramics for high-temperature processing", Journal of Heat Transfer (2010)</a>).
+ * 
+ *
+ * For image based reconstruction and redistancing see @ref example_sussman_images_3D.
+ * 
+ *
+ * First, we initialize and generate sparse grid of solid phase. In this example, an indicator funtion (color field) 
+ * would also be sufficient for defining the diffusion domain as we do not scale any parameter by their distance to the
+ * surface here, as opposed to the inhomogeneous diffusion in the CaCO\f$_3\f$ fluid phase, 
+ * see @ref 9_inhomogeneous_diffusion_porous_catalyst. 
+ * We anyways load the SDF as this gives us more flexibility when we want to extend the code towards applications 
+ * for which we need the distance to the surface.
+ *
+ * \snippet SparseGrid/10_heat_conduction_reticulate_porous_ceramics/main.cu initialize
+ *
+ * Define the functor for heat conduction
+ * \snippet SparseGrid/10_heat_conduction_reticulate_porous_ceramics/main.cu functor
+ *
+ * Iterative heat conduction
+ * \snippet SparseGrid/10_heat_conduction_reticulate_porous_ceramics/main.cu iteration
+ *
+ */
+
+//! \cond [initialize] \endcond
+#include <iostream>
+#include <typeinfo>
+
+#include "CSVReader/CSVReader.hpp"
+
+// Include redistancing files
+#include "util/PathsAndFiles.hpp"
+#include "level_set/redistancing_Sussman/RedistancingSussman.hpp"
+#include "RawReader/InitGridWithPixel.hpp"
+#include "level_set/redistancing_Sussman/HelpFunctionsForGrid.hpp" // For the ghost initialization
+#include "RemoveLines.hpp" // For removing thin (diagonal or straight) lines
+
+#include "FiniteDifference/FD_simple.hpp"
+
+#include "DiffusionSpace_sparseGrid.hpp"
+#include "HelpFunctions_diffusion.hpp"
+
+#include "Decomposition/Distribution/BoxDistribution.hpp"
+
+const size_t dims     = 3;
+
+// Property indices full grid
+const size_t PHI_FULL             = 0;
+
+typedef aggregate<float> props_full;
+
+const openfpm::vector<std::string> prop_names_full = {"Phi_Sussman_Out"};
+
+
+// Property indices sparse grid
+const size_t PHI_PHASE  = 0;
+const size_t U_N        = 1;
+const size_t U_NPLUS1   = 2;
+const size_t K_SINK     = 3;
+
+
+typedef aggregate<float, float, float, float> props_sparse;
+const openfpm::vector<std::string> prop_names_sparse = {"PHI_PHASE",
+                                                        "U_N",
+                                                        "U_NPLUS1",
+                                                        "K_SINK"};
+
+
+// Space indices
+constexpr size_t x = 0, y = 1, z = 2;
+
+// input
+const std::string path_to_redistancing_result =
+        "/MY_PATH/porous_ceramics/sussman_with_cuda/build/output_sussman_sparse_grid_porousCeramics_1216x1016x941/";
+
+
+const std::string redistancing_filename = "sparseGrid_initial.hdf5";
+        
+const std::string path_to_size = path_to_redistancing_result;
+
+const std::string output_name = "output_heat_conduction_porous_ceramics_1216x1016x941_sparse_grid";
+        
+
+int main(int argc, char* argv[])
+{
+    // Initialize library.
+    openfpm_init(&argc, &argv);
+    auto & v_cl = create_vcluster();
+
+    timer t_total;
+    timer t_iterative_diffusion_total;
+    timer t_iteration_wct;
+    timer t_GPU;
+
+
+    t_total.start();
+    
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Set current working directory, define output paths and create folders where output will be saved
+    std::string cwd                     = get_cwd();
+    const std::string path_output       = cwd + "/" + output_name + "/";
+    create_directory_if_not_exist(path_output);
+
+    if(v_cl.rank()==0) std::cout << "Redistancing result will be loaded from " << path_to_redistancing_result << redistancing_filename << std::endl;
+
+    if(v_cl.rank()==0) std::cout << "Outputs will be saved to " << path_output << std::endl;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////////////////////
+    // Create full grid and load redistancing result
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Read size of sussman redistancing output grid from csv files
+    openfpm::vector<size_t> v_sz;
+    openfpm::vector<float> v_L;
+
+    size_t m, n;
+    read_csv_to_vector(path_to_size + "/N.csv", v_sz, m, n);
+    read_csv_to_vector(path_to_size + "/L.csv", v_L, m, n);
+
+    const float Lx_low   = 0.0;
+    const float Lx_up    = v_L.get(x);
+    const float Ly_low   = 0.0;
+    const float Ly_up    = v_L.get(y);
+    const float Lz_low   = 0.0;
+    const float Lz_up    = v_L.get(z);
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    size_t sz[dims] = {v_sz.get(x), v_sz.get(y), v_sz.get(z)};
+    Box<dims, float> box({Lx_low, Ly_low, Lz_low}, {Lx_up, Ly_up, Lz_up});
+    Ghost<dims, long int> ghost(1);
+
+    typedef grid_dist_id<dims, float, props_full> grid_in_type;
+    grid_in_type g_dist(sz, box, ghost);
+
+    std::cout << "Rank " << v_cl.rank() <<  " starts loading redistancing result..." << std::endl;
+    g_dist.load(path_to_redistancing_result + "/" + redistancing_filename); // Load SDF
+    std::cout << "Rank " << v_cl.rank() <<  " finished loading redistancing result." << std::endl;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Get sparse grid of heat conduction domain
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Create sparse grid
+    std::cout << "Rank " << v_cl.rank() <<  " starts creating sparse grid." << std::endl;
+    typedef CartDecomposition<dims,float, CudaMemory, memory_traits_inte, BoxDistribution<dims,float> > Dec;
+
+    typedef sgrid_dist_id_gpu<dims, float, props_sparse> sparse_grid_type;
+    sparse_grid_type g_sparse(sz, box, ghost);
+    g_sparse.setPropNames(prop_names_sparse);
+    
+
+    const float d_low = 0.0, d_up = Lx_up;
+    get_diffusion_domain_sparse_grid<PHI_FULL, PHI_PHASE>(g_dist, g_sparse, d_low, d_up);
+    // g_sparse.load(path_to_redistancing_result + "/" + redistancing_filename);
+    std::cout << "Rank " << v_cl.rank() <<  " finished creating sparse grid." << std::endl;
+    
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Define parameters for heat conduction
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    const float u0          = 1.0; // Initial heat from one side
+    const float D           = 0.1; // Heat diffusivity
+    const float sink_rate   = 0.1; // Rate of heat loss at solid-air interface
+    std::cout << "Diffusion coefficient in s/(mm^2): " << D << std::endl;
+    std::cout << "Sink rate in 1/s: " << sink_rate << std::endl;
+
+    // Set initial condition: initial heat source is a sphere of radius R at the domain center
+    const float center[dims] = {(Lx_up+Lx_low)/(float)2,
+                                (Ly_up+Ly_low)/(float)2,
+                                (Lz_up+Lz_low)/(float)2};
+    const float R = Lz_up / 4.0;
+
+    auto dom = g_sparse.getDomainIterator();
+    while(dom.isNext())
+    {
+        auto key = dom.get();
+        
+        // Initial condition: heat comes from sphere at center  
+        Point<dims, float> coords = g_sparse.getPos(key);
+
+        if(((coords.get(x) - center[x]) * (coords.get(x) - center[x])
+          + (coords.get(y) - center[y]) * (coords.get(y) - center[y])
+          + (coords.get(z) - center[z]) * (coords.get(z) - center[z])) <= R * R) 
+        {
+            g_sparse.template insertFlush<U_N>(key) = u0;
+        }
+        else g_sparse.template insertFlush<U_N>(key) = 0.0;
+        
+        ++dom;
+    }
+
+    //! \cond [initialize] \endcond
+
+    //! \cond [functor] \endcond
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Defining the functor that will be passed to the GPU to simulate reaction-diffusion
+    // GetCpBlockType<typename SGridGpu, unsigned int prp, unsigned int stencil_size>
+    typedef typename GetCpBlockType<decltype(g_sparse),0,1>::type CpBlockType;
+    
+    const float dx = g_sparse.spacing(x), dy = g_sparse.spacing(y), dz = g_sparse.spacing(z);
+    // Stability criterion for FTCS : dt = 1/4 * 1/(1/dx^2 + 1/dy^2)
+    const float dt = diffusion_time_step(g_sparse, D);
+    std::cout << "dx, dy, dz = " << g_sparse.spacing(x) << "," << g_sparse.spacing(y) << "," << g_sparse.spacing(z) << std::endl;
+    std::cout << "dt = 1/(4D) * 1/(1/dx^2 + 1/dy^2  + 1/dz^2) = " << dt << std::endl;
+
+    auto func_heatDiffusion_withSink = [dx, dy, dz, dt, d_low, D, sink_rate] __device__ (
+            float & u_out,          // concentration output
+            float & phi_out,        // sdf of domain output (dummy)
+            CpBlockType & u,        // concentration input 
+            CpBlockType & phi,      // sdf of domain (for boundary conditions)
+            auto & block,
+            int offset,
+            int i,
+            int j,
+            int k
+    )
+    {
+        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
+        // Stencil
+        // Concentration
+        float u_c  = u(i, j, k);
+        float u_px = u(i+1, j, k);
+        float u_mx = u(i-1, j, k);
+        float u_py = u(i, j+1, k);
+        float u_my = u(i, j-1, k);
+        float u_pz = u(i, j, k+1);
+        float u_mz = u(i, j, k-1);
+        
+        // Signed distance function
+        float phi_c  = phi(i, j, k);
+        float phi_px = phi(i+1, j, k);
+        float phi_mx = phi(i-1, j, k);
+        float phi_py = phi(i, j+1, k);
+        float phi_my = phi(i, j-1, k);
+        float phi_pz = phi(i, j, k+1);
+        float phi_mz = phi(i, j, k-1);
+
+            // Get sink term
+            float k_sink = 0; // Sink equal to zero in bulk and equal to sink_rate only at the surfaces
+    
+        // Impose no-flux boundary conditions and sink term at the surface (i.e., when neighbor is outside)
+        if (phi_px <= d_low + std::numeric_limits<float>::epsilon()) {u_px = u_c; k_sink = sink_rate;}
+        if (phi_mx <= d_low + std::numeric_limits<float>::epsilon()) {u_mx = u_c; k_sink = sink_rate;}
+        if (phi_py <= d_low + std::numeric_limits<float>::epsilon()) {u_py = u_c; k_sink = sink_rate;}
+        if (phi_my <= d_low + std::numeric_limits<float>::epsilon()) {u_my = u_c; k_sink = sink_rate;}
+        if (phi_pz <= d_low + std::numeric_limits<float>::epsilon()) {u_pz = u_c; k_sink = sink_rate;}
+        if (phi_mz <= d_low + std::numeric_limits<float>::epsilon()) {u_mz = u_c; k_sink = sink_rate;}
+        
+        block.template get<K_SINK>()[offset] = k_sink; // Just for checking in paraview
+        
+        // Compute concentration of next time point
+        u_out = u_c + dt * ( D * ((u_px + u_mx - 2 * u_c)/(dx * dx)
+                                + (u_py + u_my - 2 * u_c)/(dy * dy)
+                                + (u_pz + u_mz - 2 * u_c)/(dz * dz))
+                                - k_sink * u_c);
+
+
+        // dummy outs
+        phi_out = phi_c;
+    };
+    
+    //! \cond [functor] \endcond
+
+
+    //! \cond [iteration] \endcond
+    // Create text file to which wall clock time for iteration incl. ghost communication will be saved
+    std::string path_time_filewct = path_output + "/time_wct_incl_ghostCommunication" + std::to_string(v_cl.rank()) + ".txt";
+    std::ofstream out_wct_file(path_time_filewct, std::ios_base::app);
+
+    // Create text file to which GPU time will be saved
+    std::string path_time_filegpu = path_output + "/time_GPU" + std::to_string(v_cl.rank()) + ".txt";
+    std::ofstream out_GPUtime_file(path_time_filegpu, std::ios_base::app);
+
+    
+    // Iterative diffusion
+    // const size_t iterations = 1e6;
+    const size_t iterations = 103;
+    const size_t number_write_outs = 1;
+    const size_t interval_write = iterations / number_write_outs; // Find interval for writing to reach
+    size_t iter = 0;
+    float t = 0;
+
+    // Copy from host to GPU for simulation
+    g_sparse.template hostToDevice<U_N, U_NPLUS1, K_SINK, PHI_PHASE>();
+    t_iterative_diffusion_total.start();
+    while(iter <= iterations)
+    {
+        if (iter % 2 == 0)
+        {
+            t_iteration_wct.start();
+            g_sparse.template ghost_get<PHI_PHASE, U_N, K_SINK>(RUN_ON_DEVICE);
+            t_GPU.startGPU();
+            g_sparse.template conv2_b<
+                    U_N,
+                    PHI_PHASE,
+                    U_NPLUS1,
+                    PHI_PHASE, 1>
+                    ({0, 0, 0}, 
+                    {(long int) sz[x]-1, (long int) sz[y]-1, (long int) sz[z]-1}, 
+                    func_heatDiffusion_withSink);
+                    t_GPU.stopGPU();
+                    t_iteration_wct.stop();
+            
+                    // Write out time to text-file
+                    out_wct_file << t_iteration_wct.getwct() << ",";
+                    out_GPUtime_file << t_GPU.getwctGPU() << ",";
+
+            t_iteration_wct.reset();
+        }
+        else
+        {
+            t_iteration_wct.start();
+            g_sparse.template ghost_get<PHI_PHASE, U_NPLUS1, K_SINK>(RUN_ON_DEVICE);
+            t_GPU.startGPU();
+            g_sparse.template conv2_b<
+                    U_NPLUS1,
+                    PHI_PHASE,
+                    U_N,
+                    PHI_PHASE, 1>
+                    ({0, 0, 0}, 
+                    {(long int) sz[x]-1, (long int) sz[y]-1, (long int) sz[z]-1}, 
+                    func_heatDiffusion_withSink);
+            t_GPU.stopGPU();
+                t_iteration_wct.stop();
+            
+                // Write out time to text-file
+                    out_wct_file << t_iteration_wct.getwct() << ",";
+                    out_GPUtime_file << t_GPU.getwctGPU() << ",";
+
+            t_iteration_wct.reset();
+        }
+#if 0
+        
+        if (iter % interval_write == 0)
+        {
+            // Copy from GPU to host for writing
+            g_sparse.template deviceToHost<U_N, U_NPLUS1, K_SINK, PHI_PHASE>();
+            
+            // Write g_sparse to vtk
+            g_sparse.write_frame(path_output + "g_sparse_diffuse", iter, FORMAT_BINARY);
+            
+            if (iter % 2 == 0)
+            {
+                monitor_total_concentration<U_N>(g_sparse, t, iter, path_output,"total_conc.csv");
+            }
+            else
+            {
+                monitor_total_concentration<U_NPLUS1>(g_sparse, t, iter, path_output,"total_conc.csv");
+            }
+        }
+#endif
+        t += dt;
+        iter += 1;
+    }
+    t_iterative_diffusion_total.stop();
+    std::cout << "Rank " << v_cl.rank() <<  " total time for iterative diffusion incl. write outs: " << t_iterative_diffusion_total.getwct() << std::endl;
+    std::cout << "Rank " << v_cl.rank() <<  " finished diffusion." << std::endl;
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    t_total.stop();
+    std::cout << "Rank " << v_cl.rank() <<  " total time for the whole programm : " << t_total.getwct() << std::endl;
+
+
+    openfpm_finalize();
+    return 0;
+}
+
+//! \cond [iteration] \endcond
diff --git a/example/SparseGrid/9_inhomogeneous_diffusion_porous_catalyst_CaCO3/main.cu b/example/SparseGrid/9_inhomogeneous_diffusion_porous_catalyst_CaCO3/main.cu
index b607f52d5cbd5e0f7022882eead120535a5746bc..c0d1136e580ae228069a7ea7e28d87e49853b2e4 100755
--- a/example/SparseGrid/9_inhomogeneous_diffusion_porous_catalyst_CaCO3/main.cu
+++ b/example/SparseGrid/9_inhomogeneous_diffusion_porous_catalyst_CaCO3/main.cu
@@ -4,15 +4,15 @@
 /**
  * @file 9_inhomogeneous_diffusion_porous_catalyst/main.cu
  * @page 9_inhomogeneous_diffusion_porous_catalyst diffusion_porous_media 3D
- *
+ * [TOC]
  * # Simulate inhomogeneous diffusion in a CaCO\f$_3\f$ particle packed bed #
  *
  * In this example, we simulate inhomogeneous diffusion in the fluid phase of a CaCO\f$_3\f$ particle packed bed. For the complete image-based simulation pipeline and performance of this simulation, we refer to
  * <a href="https://arxiv.org/abs/2304.11165">J. Stark, I. F. Sbalzarini "An open-source pipeline for solving continuous reaction-diffusion models in image-based geometries of porous media." arXiv preprint arXiv:2304.11165 (2023).</a>
 
- * The geometry of the fluid phase is reconstructed based on $\upmu$CT images provided by kindly provided by Prof. Jörg Petrasch (Michigan State University, College of Engineering) (see: <a href="https://asmedigitalcollection.asme.org/heattransfer/article/131/7/072701/444766/Tomographic-Characterization-of-a-Semitransparent">S. Haussener et al. “Tomographic characterization of a semitransparent-particle packed bed and determination of its thermal radiative properties”. In: Journal of Heat Transfer 131.7 (2009)</a>).
+ * The geometry of the fluid phase is reconstructed based on $\upmu$CT images provided by kindly provided by Prof. Jörg Petrasch (Michigan State University, College of Engineering) (see: <a href="https://asmedigitalcollection.asme.org/heattransfer/article/131/7/072701/444766/Tomographic-Characterization-of-a-Semitransparent">S. Haussener et al., “Tomographic characterization of a semitransparent-particle packed bed and determination of its thermal radiative properties”. In: Journal of Heat Transfer 131.7 (2009)</a>).
 
- * For image based reconstruction and redistancing see @ref example_sussman_images_3D.
+ * For image based reconstruction and redistancing, see @ref example_sussman_images_3D.
  *
  */