diff --git a/include/sfg/IndexVectors.hpp b/include/sfg/SparseIteration.hpp
similarity index 100%
rename from include/sfg/IndexVectors.hpp
rename to include/sfg/SparseIteration.hpp
diff --git a/src/sfg_walberla/api.py b/src/sfg_walberla/api.py
index b6a1aeb7dcb177cd62adf1572580b74093511033..0be30b8dc161a51ba386b036ec897fbc019f5b5c 100644
--- a/src/sfg_walberla/api.py
+++ b/src/sfg_walberla/api.py
@@ -305,7 +305,7 @@ def glfield(field: Field, ci: str | AugExpr | None = None):
 
 class IndexListBufferPtr(SrcField):
     _template = cpptype(
-        "walberla::sfg::internal::IndexListBuffer< {IndexStruct} >", "sfg/IndexVectors.hpp"
+        "walberla::sfg::internal::IndexListBuffer< {IndexStruct} >", "sfg/SparseIteration.hpp"
     )
 
     def __init__(self, idx_struct: PsStructType):
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b75a729e6afd2fc0c610b87bdc10d746850297df..59a075a72448d40f6743ba8e7bfbcd15e8e71bc8 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -15,5 +15,3 @@ add_subdirectory(${CMAKE_SOURCE_DIR}/.. ${CMAKE_BINARY_DIR}/sfg-walberla)
 
 #   Test Directories
 include(CTest)
-
-add_subdirectory( SparseIteration )
diff --git a/tests/SparseIteration/CMakeLists.txt b/tests/SparseIteration/CMakeLists.txt
deleted file mode 100644
index 23666fabf4d41684ad3489000ac15993ab841fac..0000000000000000000000000000000000000000
--- a/tests/SparseIteration/CMakeLists.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-
-add_executable( TestSparseSweeps TestSparseSweeps.cpp )
-walberla_generate_sources( TestSparseSweeps SCRIPTS SparseSweep.py )
-target_link_libraries( TestSparseSweeps PRIVATE core blockforest field sfg_walberla )
-
-add_test( NAME TestSparseSweeps COMMAND TestSparseSweeps )
diff --git a/user_manual/CMakeLists.txt b/user_manual/CMakeLists.txt
index 59c36c295de0103a5eb5657faee2ff1dfadc1fb4..fa52a83142e2873531d9438f09e31c4c2a86e7bb 100644
--- a/user_manual/CMakeLists.txt
+++ b/user_manual/CMakeLists.txt
@@ -13,5 +13,4 @@ FetchContent_MakeAvailable(walberla)
 
 add_subdirectory(${CMAKE_SOURCE_DIR}/.. ${CMAKE_BINARY_DIR}/sfg-walberla)
 
-add_subdirectory( GeneratorScriptBasics )
-add_subdirectory( ForceDrivenChannel )
+add_subdirectory( examples )
diff --git a/user_manual/FullyPeriodicAde/AdvectionDiffusionSweep.py b/user_manual/FullyPeriodicAde/AdvectionDiffusionSweep.py
deleted file mode 100644
index 876da3b324faa49e13a18a589161ffe08dfb204a..0000000000000000000000000000000000000000
--- a/user_manual/FullyPeriodicAde/AdvectionDiffusionSweep.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from pystencilssfg import SourceFileGenerator
-
-from sfg_walberla import Sweep
-from sfg_walberla.symbolic import cell
-
-
-with SourceFileGenerator() as sfg:
-    pass
diff --git a/user_manual/FullyPeriodicAde/CMakeLists.txt b/user_manual/FullyPeriodicAde/CMakeLists.txt
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/user_manual/Makefile b/user_manual/Makefile
index d931dfac6dcefe86452b20b310c390c0ea648d86..a25a0fb5753254bcfe5f61266a6b7655c85f6ce0 100644
--- a/user_manual/Makefile
+++ b/user_manual/Makefile
@@ -9,6 +9,7 @@ SOURCEDIR     = .
 BUILDDIR      = _sphinx_build
 
 ZIPPED_EXAMPLES := zipped-examples
+EXAMPLES_DIR := examples
 
 include examples.mk
 
@@ -33,7 +34,7 @@ clean:
 
 ZipExamples: $(foreach example, $(EXAMPLES), $(ZIPPED_EXAMPLES)/$(example).zip)
 
-$(ZIPPED_EXAMPLES)/%.zip: %/*
+$(ZIPPED_EXAMPLES)/%.zip: $(EXAMPLES_DIR)/%/*
 	@$(dir_guard)
 	@echo Zipping $(<D)
 	@zip -r $@ $(<D)
diff --git a/user_manual/conf.py b/user_manual/conf.py
index cfa54f38b4a54d40be07e51cb183f088bdf36c32..01a1b95a1eac67d6ded174aa8f4ee18ff8a59e2c 100644
--- a/user_manual/conf.py
+++ b/user_manual/conf.py
@@ -6,7 +6,7 @@
 # -- Project information -----------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
 
-project = 'waLBerla next-codegen'
+project = 'sfg-waLBerla'
 copyright = '2024, Frederik Hennig'
 author = 'Frederik Hennig'
 
diff --git a/user_manual/examples.mk b/user_manual/examples.mk
index 2d78f20423a18f80a6bc70282b541fdaee4ac96d..4ab97ae0510e8b4cebe7a5b73de8313a6a88447b 100644
--- a/user_manual/examples.mk
+++ b/user_manual/examples.mk
@@ -1 +1 @@
-EXAMPLES = GeneratorScriptBasics ForceDrivenChannel
+EXAMPLES = GeneratorScriptBasics SparseSpiral ForceDrivenChannel
diff --git a/user_manual/examples/CMakeLists.txt b/user_manual/examples/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b1419920a911b2eff471d98ad8429d8fc44c51fd
--- /dev/null
+++ b/user_manual/examples/CMakeLists.txt
@@ -0,0 +1,3 @@
+add_subdirectory( GeneratorScriptBasics )
+add_subdirectory( ForceDrivenChannel )
+add_subdirectory( SparseSpiral )
\ No newline at end of file
diff --git a/user_manual/ForceDrivenChannel/CMakeLists.txt b/user_manual/examples/ForceDrivenChannel/CMakeLists.txt
similarity index 100%
rename from user_manual/ForceDrivenChannel/CMakeLists.txt
rename to user_manual/examples/ForceDrivenChannel/CMakeLists.txt
diff --git a/user_manual/ForceDrivenChannel/Channel.prm b/user_manual/examples/ForceDrivenChannel/Channel.prm
similarity index 100%
rename from user_manual/ForceDrivenChannel/Channel.prm
rename to user_manual/examples/ForceDrivenChannel/Channel.prm
diff --git a/user_manual/ForceDrivenChannel/ForceDrivenChannel.cpp b/user_manual/examples/ForceDrivenChannel/ForceDrivenChannel.cpp
similarity index 100%
rename from user_manual/ForceDrivenChannel/ForceDrivenChannel.cpp
rename to user_manual/examples/ForceDrivenChannel/ForceDrivenChannel.cpp
diff --git a/user_manual/ForceDrivenChannel/ForceDrivenChannel.md b/user_manual/examples/ForceDrivenChannel/ForceDrivenChannel.md
similarity index 100%
rename from user_manual/ForceDrivenChannel/ForceDrivenChannel.md
rename to user_manual/examples/ForceDrivenChannel/ForceDrivenChannel.md
diff --git a/user_manual/ForceDrivenChannel/LbmAlgorithms.py b/user_manual/examples/ForceDrivenChannel/LbmAlgorithms.py
similarity index 100%
rename from user_manual/ForceDrivenChannel/LbmAlgorithms.py
rename to user_manual/examples/ForceDrivenChannel/LbmAlgorithms.py
diff --git a/user_manual/GeneratorScriptBasics/BasicCodegen.py b/user_manual/examples/GeneratorScriptBasics/BasicCodegen.py
similarity index 100%
rename from user_manual/GeneratorScriptBasics/BasicCodegen.py
rename to user_manual/examples/GeneratorScriptBasics/BasicCodegen.py
diff --git a/user_manual/GeneratorScriptBasics/BasicCodegenApp.cpp b/user_manual/examples/GeneratorScriptBasics/BasicCodegenApp.cpp
similarity index 100%
rename from user_manual/GeneratorScriptBasics/BasicCodegenApp.cpp
rename to user_manual/examples/GeneratorScriptBasics/BasicCodegenApp.cpp
diff --git a/user_manual/GeneratorScriptBasics/CMakeLists.txt b/user_manual/examples/GeneratorScriptBasics/CMakeLists.txt
similarity index 100%
rename from user_manual/GeneratorScriptBasics/CMakeLists.txt
rename to user_manual/examples/GeneratorScriptBasics/CMakeLists.txt
diff --git a/user_manual/GeneratorScriptBasics/GeneratorScriptBasics.md b/user_manual/examples/GeneratorScriptBasics/GeneratorScriptBasics.md
similarity index 100%
rename from user_manual/GeneratorScriptBasics/GeneratorScriptBasics.md
rename to user_manual/examples/GeneratorScriptBasics/GeneratorScriptBasics.md
diff --git a/user_manual/examples/SparseSpiral/CMakeLists.txt b/user_manual/examples/SparseSpiral/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6ec9191dc263b3eee1015fa00433cbc1987a71a1
--- /dev/null
+++ b/user_manual/examples/SparseSpiral/CMakeLists.txt
@@ -0,0 +1,5 @@
+
+add_executable( SparseSpiral SparseSpiral.cpp )
+walberla_generate_sources( SparseSpiral SCRIPTS SpiralSweep.py )
+target_link_libraries( SparseSpiral PRIVATE core blockforest field vtk sfg_walberla )
+
diff --git a/tests/SparseIteration/TestSparseSweeps.cpp b/user_manual/examples/SparseSpiral/SparseSpiral.cpp
similarity index 67%
rename from tests/SparseIteration/TestSparseSweeps.cpp
rename to user_manual/examples/SparseSpiral/SparseSpiral.cpp
index 272930b78f835fe387a86806662b1275e706ac15..f2b469b62870376f3b9aa47f5afc823a458b4dc7 100644
--- a/tests/SparseIteration/TestSparseSweeps.cpp
+++ b/user_manual/examples/SparseSpiral/SparseSpiral.cpp
@@ -2,13 +2,14 @@
 #include "blockforest/all.h"
 #include "field/GhostLayerField.h"
 #include "field/AddToStorage.h"
-#include "sfg/IndexVectors.hpp"
+#include "field/vtk/all.h"
+#include "sfg/SparseIteration.hpp"
 
 #include <array>
 
-#include "gen/TestSparseSweeps/SparseSweep.hpp"
+#include "gen/SparseSpiral/SparseSweep.hpp"
 
-namespace TestSparseSweeps
+namespace SparseSpiral
 {
     using namespace walberla;
 
@@ -17,16 +18,22 @@ namespace TestSparseSweeps
 
     using PointField = field::GhostLayerField<real_t, 3>;
 
+    constexpr real_t radius { 15.0 };
+    constexpr Vector3< real_t > center { 20, 20, 0 };
+    constexpr real_t height { 120. };
+    constexpr real_t width { 4.0 };
+
     Vector3<real_t> pointOnSpiral(real_t z)
     {
-        real_t two_pi_z{2.0 * math::pi * z};
-        return Vector3<real_t>{cos(two_pi_z), sin(two_pi_z), z};
+        real_t t{4.0 * math::pi * z / height};
+        return center + Vector3<real_t>{radius * cos(t), radius * sin(t), z};
     }
 
     void main(int argc, char **argv)
     {
+        Environment env{ argc, argv };
 
-        AABB domainAabb{0., 0., 0., 40., 40., 120.};
+        AABB domainAabb{0., 0., 0., 40., 40., height };
         array<uint_t, 3> gridSize{4, 4, 12};
         array<uint_t, 3> cpb{10, 10, 10};
 
@@ -42,18 +49,18 @@ namespace TestSparseSweeps
         {
             Vector3<real_t> globalPoint{blocks->getCellCenter(globalCell)};
             Vector3<real_t> spiralPoint{pointOnSpiral(globalPoint[2])};
-            return (globalPoint - spiralPoint).sqrLength() < 2.0;
+            return (globalPoint - spiralPoint).sqrLength() < width;
         };
 
         CellInterval allCells{{0, 0, 0}, {cell_idx_c(cpb[0]), cell_idx_c(cpb[1]), cell_idx_c(cpb[2])}};
 
         for (auto &b : *blocks)
         {
-            vector<sfg::CellIdx> idxVec{indexList.get(b)};
+            vector<sfg::CellIdx> & idxVec{indexList.get(b)};
             for (Cell c : allCells)
             {
                 Cell globalCell{c};
-                blocks->transformBlockLocalToGlobalCell(c, b);
+                blocks->transformBlockLocalToGlobalCell(globalCell, b);
                 if (isOnSpiral(globalCell))
                 {
                     idxVec.emplace_back(c);
@@ -61,7 +68,7 @@ namespace TestSparseSweeps
             }
         }
 
-        gen::SparseSetCoordinates sweep{blocks, fID, indexList.bufferId()};
+        gen::SpiralSweep sweep{blocks, fID, indexList.bufferId()};
 
         for (auto &b : *blocks)
         {
@@ -74,7 +81,7 @@ namespace TestSparseSweeps
             for (Cell c : allCells)
             {
                 Cell globalCell{c};
-                blocks->transformBlockLocalToGlobalCell(c, b);
+                blocks->transformBlockLocalToGlobalCell(globalCell, b);
                 if (isOnSpiral(c))
                 {
                     Vector3< real_t > cellCenter { blocks->getCellCenter(globalCell) };
@@ -84,11 +91,14 @@ namespace TestSparseSweeps
                 }
             }
         }
+
+        auto vtkWriter = field::createVTKOutput< PointField >(fID, *blocks, "f", 1, 0);
+        vtkWriter();
     }
 }
 
 int main(int argc, char **argv)
 {
-    TestSparseSweeps::main(argc, argv);
+    SparseSpiral::main(argc, argv);
     return 0;
-}
\ No newline at end of file
+}
diff --git a/tests/SparseIteration/SparseSweep.py b/user_manual/examples/SparseSpiral/SpiralSweep.py
similarity index 82%
rename from tests/SparseIteration/SparseSweep.py
rename to user_manual/examples/SparseSpiral/SpiralSweep.py
index 4489d3beee63edae16f324fb82c50a568134b22b..f9808a5ad79fdfa5b9444e6035e7b5773460b0ab 100644
--- a/tests/SparseIteration/SparseSweep.py
+++ b/user_manual/examples/SparseSpiral/SpiralSweep.py
@@ -4,7 +4,7 @@ from sfg_walberla.symbolic import cell
 from pystencils import fields, Assignment
 
 with SourceFileGenerator() as sfg:
-    sfg.namespace("TestSparseSweeps::gen")
+    sfg.namespace("SparseSpiral::gen")
     f = fields("f(3): [3D]", layout="fzyx")
 
     asms = [
@@ -13,7 +13,7 @@ with SourceFileGenerator() as sfg:
         Assignment(f.center(2), cell.z()),
     ]
 
-    sweep = Sweep("SparseSetCoordinates", asms)
+    sweep = Sweep("SpiralSweep", asms)
     sweep.sparse = True
 
     sfg.generate(sweep)
diff --git a/user_manual/guides/SparseSweeps.md b/user_manual/guides/SparseSweeps.md
new file mode 100644
index 0000000000000000000000000000000000000000..03fea2eb4b4a187354a0b4ebf5bfefed62ead208
--- /dev/null
+++ b/user_manual/guides/SparseSweeps.md
@@ -0,0 +1,34 @@
+# Sparse Sweeps
+
+:::{admonition} Example Code
+The code shown in this guide is taken from the *SparseSpiral* example:
+{download}`SparseSpiral.zip </zipped-examples/SparseSpiral.zip>`
+:::
+
+
+`sfg-walberla` supports the generation of sparse sweeps, which operate only on a subset of cells
+defined by a user-provided *index list*.
+
+To turn your sweep into a sparse sweep, simply set its `sparse` property to `True`.
+To illustrate, here's a sparse sweep that stores the coordinates of each cell
+in a field:
+
+```{literalinclude} ../examples/SparseSpiral/SpiralSweep.py
+:caption: SpiralSweep.py
+:lines: 10-17
+:dedent: 4
+```
+
+In your C++ code, you must set up an index list which lists all cells the sweep should operate on.
+Due to waLBerla's block-structured nature, the index list will actually be a collection of cell vectors,
+with one vector per block.
+The SFG library provides `sfg::SparseIndexList` for this, which is defined in `sfg/SparseIteration.hpp`.
+The following code sample create and populate an index list, using a prediate here called `isOnSpiral`.
+The index list's internal block data ID must then be passed to the sweep's constructor.
+
+:::{literalinclude} ../examples/SparseSpiral/SparseSpiral.cpp
+:caption: SparseSpiral.cpp
+:lines: 46,56-72
+:dedent: 8
+:language: C++
+:::
diff --git a/user_manual/index.md b/user_manual/index.md
index 473b4f1a05c50df6418adf4cad216475acf8e0dd..a48e3fe493164d216d069af31192de6f64a37b51 100644
--- a/user_manual/index.md
+++ b/user_manual/index.md
@@ -1,19 +1,13 @@
 # The Next Generation of waLBerla Code Generation
 
-Welcome to *The Next Generation of waLBerla Code Generation*.
-This book is aimed at teaching you how to use the next generation of code generators for waLBerla
-by walking through a set of example applications, each designed to highlight specific features.
-
-The next-gen code generators for waLBerla are based on bleeding-edge developments in
-[pystencils 2.0][pystencils_2_0] and [pystencils-sfg][pystencils-sfg],
-and are currently located in the separate [sfg-walberla][sfg-walberla] repository.
+Welcome to the `sfg-walberla` project,
+where the next generation of code generators for waLBerla is being developed.
+These are based on bleeding-edge developments in
+[pystencils 2.0][pystencils_2_0] and [pystencils-sfg][pystencils-sfg].
 This project is still unstable and immature, but growing steadily.
 
-Until the next-gen code generators are stabilized and merged with the waLBerla master,
-setting up a build system and development environment for working with them is slightly more
-complicated. Since you will need such an environment to follow along with the examples
-in this book, start by reading the chapter [](EnvSetup).
-Afterward, you are free to explore the remainder of the book.
+In this user manual, you will find guidance and information about how to use the new
+code generation infrastructure in your simulation applications.
 
 ## Table of Contents
 
@@ -22,14 +16,21 @@ Afterward, you are free to explore the remainder of the book.
 :maxdepth: 1
 
 CMakeSetup/CMakeSetup
-GeneratorScriptBasics/GeneratorScriptBasics
+examples/GeneratorScriptBasics/GeneratorScriptBasics
+:::
+
+:::{toctree}
+:caption: User Guides
+:maxdepth: 1
+
+guides/SparseSweeps
 :::
 
 :::{toctree}
-:caption: Basic LBM Simulations
+:caption: Examples
 :maxdepth: 1
 
-ForceDrivenChannel/ForceDrivenChannel
+examples/ForceDrivenChannel/ForceDrivenChannel
 :::
 
 :::{toctree}
diff --git a/user_manual/reference/PythonEnvironment.md b/user_manual/reference/PythonEnvironment.md
index 298d83a05835c0ff193257fbf00e0d38405b83ec..df1530814c6606a9998f221ee30dd2d125c55728 100644
--- a/user_manual/reference/PythonEnvironment.md
+++ b/user_manual/reference/PythonEnvironment.md
@@ -5,9 +5,9 @@ used by the waLBerla code generation system.
 
 ## Using the Private Virtual Environment
 
-If the CMake cache variable `WALBERLA_CODEGEN_PRIVATE_VENV` is enabled,
-`sfg-walberla` creates a new Python virtual environment within the CMake build tree,
+By default, `sfg-walberla` creates a new Python virtual environment within the CMake build tree,
 and there installs all packages required for code generation.
+This can be disabled by setting the `WALBERLA_CODEGEN_PRIVATE_VENV` CMake cache variable to `FALSE`.
 
 ### Install Additional Packages
 
@@ -26,14 +26,13 @@ editable install, or `-r <requirements-file>` to install packages from a require
 
 ## Using an External Virtual Environment
 
-To have even more control over your Python environment, you can configure sfg-walberla to
-forego creating a private virtual environment, and instead use the Python interpreter
-supplied from the outside.
+If `WALBERLA_CODEGEN_PRIVATE_VENV` is set to `FALSE`, sfg-walberla will use the Python interpreter
+found in the CMake environment for running the code generators.
+You can customize your Python interpreter by setting the `Python_EXECUTABLE` or `Python_ROOT_DIR` hints.
 
-To explicitly specify a Python interpreter, you need to set the `WALBERLA_CODEGEN_PRIVATE_VENV` cache
-variable to `FALSE`, and set `Python_EXECUTABLE` to point at your Python binary.
-For instance, at configuration time:
+:::{seealso}
+[FindPython CMake Module](https://cmake.org/cmake/help/latest/module/FindPython.html)
+:::
 
-```bash
-cmake -S . -B build -DWALBERLA_CODEGEN_PRIVATE_VENV=FALSE -DPython_EXECUTABLE=<path-to-python>
-```
+Sfg-walberla will check if the required packages are installed into the given external Python environment,
+and raise an error if any are missing.