diff --git a/lib/walberla/experimental/sweep/DomainSlices.hpp b/lib/walberla/experimental/sweep/DomainSlices.hpp
index 0492cb53bdc2baddae1f8b4f8468eea4b95bfc02..64ca0ea93a62661932f608931620a829e25a6b6d 100644
--- a/lib/walberla/experimental/sweep/DomainSlices.hpp
+++ b/lib/walberla/experimental/sweep/DomainSlices.hpp
@@ -8,9 +8,16 @@
 
 #include "stencil/Directions.h"
 
+#include <memory>
+
 namespace walberla::experimental::sweep
 {
 
+template< typename T >
+concept Sweep = requires(T obj, IBlock* block) {
+   { obj(block) } -> std::same_as< void >;
+};
+
 template< typename T >
 concept CellIntervalSweep = requires(T obj, IBlock* block, const CellInterval& ci) {
    { obj.runOnCellInterval(block, ci) } -> std::same_as< void >;
@@ -23,53 +30,116 @@ struct BorderSweepSlice
 {
    shared_ptr< StructuredBlockForest > blocks;
    cell_idx_t offset;
+   cell_idx_t expand;
 
    CellInterval ci();
    bool isBlockAtBorder(IBlock& b);
 };
 
 template<>
-CellInterval BorderSweepSlice< stencil::Direction::T >::ci()
+CellInterval BorderSweepSlice< stencil::Direction::W >::ci()
 {
-   return { { 0, 0, cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - (offset + 1) },
-            { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - 1, //
-              cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - 1, //
-              cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - (offset + 1) } };
+   return { { offset, -expand, -expand },
+            { offset,                                                       //
+              cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - 1 + expand, //
+              cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - 1 + expand } };
 };
 
 template<>
-bool BorderSweepSlice< stencil::Direction::B >::isBlockAtBorder(IBlock& b)
+CellInterval BorderSweepSlice< stencil::Direction::E >::ci()
 {
-   return blocks->atDomainZMinBorder(b);
-}
+   return { { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - (offset + 1), -expand, -expand },
+            { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - (offset + 1), //
+              cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - 1 + expand,   //
+              cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - 1 + expand } };
+};
+
+template<>
+CellInterval BorderSweepSlice< stencil::Direction::S >::ci()
+{
+   return { { -expand, offset, -expand },
+            { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - 1 + expand, //
+              offset,                                                       //
+              cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - 1 + expand } };
+};
+
+template<>
+CellInterval BorderSweepSlice< stencil::Direction::N >::ci()
+{
+   return { { -expand, cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - (offset + 1), -expand },
+            { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - 1 + expand,   //
+              cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - (offset + 1), //
+              cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - 1 + expand } };
+};
 
 template<>
 CellInterval BorderSweepSlice< stencil::Direction::B >::ci()
 {
-   return { { 0, 0, offset },
-            { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - 1, //
-              cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - 1, //
+   return { { -expand, -expand, offset },
+            { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - 1 + expand, //
+              cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - 1 + expand, //
               offset } };
 };
 
+template<>
+CellInterval BorderSweepSlice< stencil::Direction::T >::ci()
+{
+   return { { -expand, -expand, cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - (offset + 1) },
+            { cell_idx_c(blocks->getNumberOfXCellsPerBlock()) - 1 + expand, //
+              cell_idx_c(blocks->getNumberOfYCellsPerBlock()) - 1 + expand, //
+              cell_idx_c(blocks->getNumberOfZCellsPerBlock()) - (offset + 1) } };
+};
+
+template<>
+bool BorderSweepSlice< stencil::Direction::W >::isBlockAtBorder(IBlock& b)
+{
+   return blocks->atDomainXMinBorder(b);
+}
+
+template<>
+bool BorderSweepSlice< stencil::Direction::E >::isBlockAtBorder(IBlock& b)
+{
+   return blocks->atDomainXMaxBorder(b);
+}
+
+template<>
+bool BorderSweepSlice< stencil::Direction::S >::isBlockAtBorder(IBlock& b)
+{
+   return blocks->atDomainYMinBorder(b);
+}
+
+template<>
+bool BorderSweepSlice< stencil::Direction::N >::isBlockAtBorder(IBlock& b)
+{
+   return blocks->atDomainYMaxBorder(b);
+}
+
+template<>
+bool BorderSweepSlice< stencil::Direction::B >::isBlockAtBorder(IBlock& b)
+{
+   return blocks->atDomainZMinBorder(b);
+}
+
 template<>
 bool BorderSweepSlice< stencil::Direction::T >::isBlockAtBorder(IBlock& b)
 {
    return blocks->atDomainZMaxBorder(b);
 }
+
 } // namespace detail
 
-template< stencil::Direction BorderDir, CellIntervalSweep Sweep >
+template< stencil::Direction BorderDir, CellIntervalSweep Sweep_T >
 class BorderSweep
 {
  private:
-   Sweep sweep_;
+   Sweep_T sweep_;
    detail::BorderSweepSlice< BorderDir > slice_;
    CellInterval ci_;
 
  public:
-   BorderSweep(const shared_ptr< StructuredBlockForest >& blocks, const Sweep& sweep, cell_idx_t offset = 0)
-      : sweep_{ sweep }, slice_{ blocks, offset }, ci_{ slice_.ci() }
+   BorderSweep(const shared_ptr< StructuredBlockForest >& blocks, const Sweep_T& sweep, cell_idx_t offset = 0,
+               cell_idx_t expand = 0)
+      : sweep_{ sweep }, slice_{ blocks, offset, expand }, ci_{ slice_.ci() }
    {}
 
    void operator()(IBlock* block)
@@ -78,4 +148,56 @@ class BorderSweep
    }
 };
 
+template< Sweep Sw1, Sweep Sw2 >
+class FusedSweep
+{
+ private:
+   Sw1 sw1_;
+   Sw2 sw2_;
+
+ public:
+   FusedSweep(const Sw1& sw1, const Sw2& sw2) : sw1_{ sw1 }, sw2_{ sw2 } {}
+
+   void operator()(IBlock* b)
+   {
+      sw1_(b);
+      sw2_(b);
+   }
+
+   template< typename = void >
+      requires(CellIntervalSweep< Sw1 > && CellIntervalSweep< Sw2 >)
+   void runOnCellInterval(IBlock* b, const CellInterval& ci)
+   {
+      sw1_.runOnCellInterval(b, ci);
+      sw2_.runOnCellInterval(b, ci);
+   }
+};
+
+namespace detail
+{
+auto doFuse(const Sweep auto& sw1, const Sweep auto& sw2) { return FusedSweep{ sw1, sw2 }; }
+
+auto doFuse(const Sweep auto& first, const Sweep auto&... others)
+{
+   return FusedSweep{ first, doFuse(std::forward< decltype(others) >(others)...) };
+}
+} // namespace detail
+
+class SweepFactory
+{
+ private:
+   std::shared_ptr< StructuredBlockForest > blocks_;
+
+ public:
+   SweepFactory(const shared_ptr< StructuredBlockForest >& blocks) : blocks_{ blocks } {}
+
+   template< stencil::Direction Dir, CellIntervalSweep Sweep_T >
+   BorderSweep< Dir, Sweep_T > atDomainBorder(const Sweep_T& sweep, cell_idx_t offset = 0, cell_idx_t expand = 0)
+   {
+      return { blocks_, sweep, offset, expand };
+   }
+
+   auto fuse(const Sweep auto&... sweeps) { return detail::doFuse(std::forward< decltype(sweeps) >(sweeps)...); }
+};
+
 } // namespace walberla::experimental::sweep