From 45719cceb070e53717f729cb5fa44e0e2c186055 Mon Sep 17 00:00:00 2001
From: markus holzer <markus.holzer@fau.de>
Date: Mon, 12 Oct 2020 20:44:37 +0200
Subject: [PATCH] Started rebasing

---
 src/lbm/all.h                                 |    1 +
 src/lbm/refinement_rebase/BoundarySetup.h     |  206 ++
 .../EqualLevelBorderStreamCorrection.h        |  171 ++
 src/lbm/refinement_rebase/LinearExplosion.h   |  498 +++++
 src/lbm/refinement_rebase/PdfFieldPackInfo.h  | 1452 ++++++++++++++
 .../refinement_rebase/PdfFieldSyncPackInfo.h  |   44 +
 .../RefinementFunctorWrapper.h                |   67 +
 src/lbm/refinement_rebase/TimeStep.h          | 1751 +++++++++++++++++
 .../refinement_rebase/TimeStepPdfPackInfo.h   |   44 +
 src/lbm/refinement_rebase/TimeTracker.h       |  109 +
 .../VorticityBasedLevelDetermination.h        |  161 ++
 src/lbm/refinement_rebase/all.h               |   32 +
 tests/lbm/CMakeLists.txt                      |    3 +-
 .../lbm/codegen/CodeGenerationRefinement.cpp  |   63 +-
 14 files changed, 4570 insertions(+), 32 deletions(-)
 create mode 100644 src/lbm/refinement_rebase/BoundarySetup.h
 create mode 100644 src/lbm/refinement_rebase/EqualLevelBorderStreamCorrection.h
 create mode 100644 src/lbm/refinement_rebase/LinearExplosion.h
 create mode 100644 src/lbm/refinement_rebase/PdfFieldPackInfo.h
 create mode 100644 src/lbm/refinement_rebase/PdfFieldSyncPackInfo.h
 create mode 100644 src/lbm/refinement_rebase/RefinementFunctorWrapper.h
 create mode 100644 src/lbm/refinement_rebase/TimeStep.h
 create mode 100644 src/lbm/refinement_rebase/TimeStepPdfPackInfo.h
 create mode 100644 src/lbm/refinement_rebase/TimeTracker.h
 create mode 100644 src/lbm/refinement_rebase/VorticityBasedLevelDetermination.h
 create mode 100644 src/lbm/refinement_rebase/all.h

diff --git a/src/lbm/all.h b/src/lbm/all.h
index d4b0a4f15..91b01b6b0 100644
--- a/src/lbm/all.h
+++ b/src/lbm/all.h
@@ -35,5 +35,6 @@
 #include "gui/all.h"
 #include "lattice_model/all.h"
 #include "refinement/all.h"
+#include "refinement_rebase/all.h"
 #include "sweeps/all.h"
 #include "vtk/all.h"
diff --git a/src/lbm/refinement_rebase/BoundarySetup.h b/src/lbm/refinement_rebase/BoundarySetup.h
new file mode 100644
index 000000000..4271b9984
--- /dev/null
+++ b/src/lbm/refinement_rebase/BoundarySetup.h
@@ -0,0 +1,206 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file LinearExplosion.h
+//! \ingroup lbm
+//! \author Florian Schornbaum <florian.schornbaum@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "blockforest/Block.h"
+#include "blockforest/BlockNeighborhoodSection.h"
+
+#include "boundary/Boundary.h"
+
+#include "core/cell/CellInterval.h"
+#include "core/debug/Debug.h"
+
+#include "domain_decomposition/StructuredBlockStorage.h"
+
+#include "field/FlagUID.h"
+
+#include "stencil/D3Q27.h"
+
+
+
+namespace walberla {
+namespace lbm {
+namespace refinement {
+
+
+namespace internal {
+inline shared_ptr< BoundaryConfiguration > defaultBoundaryConfiguration( const Cell &, const Vector3< real_t > & )
+{
+   return shared_ptr< BoundaryConfiguration >( new BoundaryConfiguration );
+}
+}
+
+
+//**********************************************************************************************************************
+/*!
+*   \brief Function for consistently setting boundaries across block borders (even if there is a level transition)
+*
+*   Boundaries are set not just in the inside of the block that is passed to this function, but also in the block's
+*   ghost layers.
+*   The user must provide a callback function "isBoundary": For any point in the 3D simulation space, this function must
+*   either return true or false. Returning true means at this point in space, a boundary of type "flag" must be set.
+*   If the boundary requires a corresponding boundary configuration parameter, a second callback function "parameter"
+*   must be provided: If a boundary is set at a certain cell, this cell is passed to the callback function together
+*   with the point in the 3D simulation space that, prior to that, was passed to "isBoundary". The function then must
+*   return a shared pointer to a BoundaryConfiguration object.
+*
+*   \param blocks              block storage data structure
+*   \param block               block for which boundaries of type "flag" will be set
+*   \param boundaryHandlingId  block data ID for the boundary handling object
+*   \param flag                flag UID that corresponds to the boundary which will be set
+*   \param isBoundary          callback function for determining if for a certain point in the 3D simulation space a
+*                              boundary corresponding to "flag" must be set
+*   \param parameter           callback function for retrieving boundary configuration parameters
+*/
+//**********************************************************************************************************************
+template< typename BoundaryHandling_T, bool ForceBoundary >
+void consistentlySetBoundary( StructuredBlockStorage & blocks, Block & block, const BlockDataID & boundaryHandlingId, const FlagUID & flag,
+                              const std::function< bool ( const Vector3< real_t > & ) > & isBoundary,
+                              const std::function< shared_ptr< BoundaryConfiguration > ( const Cell &, const Vector3< real_t > & ) > & parameter = internal::defaultBoundaryConfiguration );
+
+
+
+/// For documentation see function "template< typename BoundaryHandling_T, bool ForceBoundary > void consistentlySetBoundary(...)"
+template< typename BoundaryHandling_T >
+void consistentlySetBoundary( StructuredBlockStorage & blocks, Block & block, const BlockDataID & boundaryHandlingId, const FlagUID & flag,
+                              const std::function< bool ( const Vector3< real_t > & ) > & isBoundary,
+                              const std::function< shared_ptr< BoundaryConfiguration > ( const Cell &, const Vector3< real_t > & ) > & parameter = internal::defaultBoundaryConfiguration )
+{
+   consistentlySetBoundary< BoundaryHandling_T, false >( blocks, block, boundaryHandlingId, flag, isBoundary, parameter );
+}
+
+
+
+/// For documentation see function "template< typename BoundaryHandling_T, bool ForceBoundary > void consistentlySetBoundary(...)"
+template< typename BoundaryHandling_T >
+void consistentlyForceBoundary( StructuredBlockStorage & blocks, Block & block, const BlockDataID & boundaryHandlingId, const FlagUID & flag,
+                                const std::function< bool ( const Vector3< real_t > & ) > & isBoundary,
+                                const std::function< shared_ptr< BoundaryConfiguration > ( const Cell &, const Vector3< real_t > & ) > & parameter = internal::defaultBoundaryConfiguration )
+{
+   consistentlySetBoundary< BoundaryHandling_T, true >( blocks, block, boundaryHandlingId, flag, isBoundary, parameter );
+}
+
+
+
+template< typename BoundaryHandling_T, bool ForceBoundary >
+void consistentlySetBoundary( StructuredBlockStorage & blocks, Block & block, const BlockDataID & boundaryHandlingId, const FlagUID & flag,
+                              const std::function< bool ( const Vector3< real_t > & ) > & isBoundary,
+                              const std::function< shared_ptr< BoundaryConfiguration > ( const Cell &, const Vector3< real_t > & ) > & parameter )
+{
+   const uint_t level = blocks.getLevel(block);
+
+   BoundaryHandling_T * boundaryHandling = block.template getData< BoundaryHandling_T >( boundaryHandlingId );
+
+   const auto * const flagField = boundaryHandling->getFlagField();
+   int ghostLayers = int_c( flagField->nrOfGhostLayers() );
+
+   CellInterval cells = flagField->xyzSize();
+   cells.expand( cell_idx_c(ghostLayers) );
+
+   std::vector< CellInterval > coarseRegions;
+   for( auto dir = stencil::D3Q27::beginNoCenter(); dir != stencil::D3Q27::end(); ++dir )
+   {
+      const auto index = blockforest::getBlockNeighborhoodSectionIndex( dir.cx(), dir.cy(), dir.cz() );
+      if( block.neighborhoodSectionHasLargerBlock( index ) )
+      {
+         CellInterval coarseRegion( cells );
+         for( uint_t i = 0; i != 3; ++i )
+         {
+            const auto c = stencil::c[i][*dir];
+
+            if( c == -1 ) coarseRegion.max()[i] = coarseRegion.min()[i] + cell_idx_c( 2 * ghostLayers - 1 );
+            else if( c == 1 ) coarseRegion.min()[i] = coarseRegion.max()[i] - cell_idx_c( 2 * ghostLayers - 1 );
+         }
+         coarseRegions.push_back( coarseRegion );
+      }
+   }
+
+   for( auto cell = cells.begin(); cell != cells.end(); ++cell )
+   {
+      bool inCoarseRegion( false );
+      for( auto region = coarseRegions.begin(); region != coarseRegions.end() && !inCoarseRegion; ++region )
+         inCoarseRegion = region->contains( *cell );
+
+      if( !inCoarseRegion )
+      {
+         Vector3< real_t > center;
+         blocks.getBlockLocalCellCenter( block, *cell, center );
+         blocks.mapToPeriodicDomain( center );
+
+         if( isBoundary(center) )
+         {
+            if( ForceBoundary )
+            {
+               auto p = parameter( *cell, center );
+               boundaryHandling->forceBoundary( flag, cell->x(), cell->y(), cell->z(), *p );
+            }
+            else
+            {
+               auto p = parameter( *cell, center );
+               boundaryHandling->setBoundary( flag, cell->x(), cell->y(), cell->z(), *p );
+            }
+         }
+      }
+      else
+      {
+         Cell globalCell( *cell );
+         blocks.transformBlockLocalToGlobalCell( globalCell, block );
+
+         Cell coarseCell( globalCell );
+         for( uint_t i = 0; i < 3; ++i )
+         {
+            if( coarseCell[i] < cell_idx_t(0) )
+            {
+               coarseCell[i] = -( ( cell_idx_t(1) - coarseCell[i] ) >> 1 );
+            }
+            else
+            {
+               coarseCell[i] >>= 1;
+            }
+         }
+
+         Vector3< real_t > coarseCenter;
+         blocks.getCellCenter( coarseCenter, coarseCell, level - uint_t(1) );
+         blocks.mapToPeriodicDomain( coarseCenter );
+
+         if( isBoundary(coarseCenter) )
+         {
+            if( ForceBoundary )
+            {
+               auto p = parameter( *cell, coarseCenter );            
+               boundaryHandling->forceBoundary( flag, cell->x(), cell->y(), cell->z(), *p );
+            }
+            else
+            {
+               auto p = parameter( *cell, coarseCenter );
+               boundaryHandling->setBoundary( flag, cell->x(), cell->y(), cell->z(), *p );
+            }
+         }
+      }
+   }
+}
+
+
+
+} // namespace refinement
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/EqualLevelBorderStreamCorrection.h b/src/lbm/refinement_rebase/EqualLevelBorderStreamCorrection.h
new file mode 100644
index 000000000..2cc428b98
--- /dev/null
+++ b/src/lbm/refinement_rebase/EqualLevelBorderStreamCorrection.h
@@ -0,0 +1,171 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file EqualLevelBorderStreamCorrection.h
+//! \ingroup lbm
+//! \author Florian Schornbaum <florian.schornbaum@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "lbm/field/PdfField.h"
+
+#include "blockforest/Block.h"
+#include "blockforest/BlockNeighborhoodSection.h"
+
+#include "core/cell/CellInterval.h"
+
+#include "stencil/D2CornerStencil.h"
+#include "stencil/D3EdgeCornerStencil.h"
+
+#include <type_traits>
+
+
+
+namespace walberla {
+namespace lbm {
+namespace refinement {
+
+
+
+namespace internal {
+template< typename LatticeModel_T, class Enable = void  >
+struct EdgeCornerStencil;
+template< typename LatticeModel_T >
+struct EdgeCornerStencil< LatticeModel_T, typename std::enable_if< LatticeModel_T::Stencil::D == 2 >::type >
+{
+   typedef stencil::D2CornerStencil type;
+};
+template< typename LatticeModel_T >
+struct EdgeCornerStencil< LatticeModel_T, typename std::enable_if< LatticeModel_T::Stencil::D == 3 >::type >
+{
+   typedef stencil::D3EdgeCornerStencil type;
+};
+}
+
+
+
+template< typename LatticeModel_T >
+class EqualLevelBorderStreamCorrection
+{
+public:
+
+   typedef PdfField< LatticeModel_T > PdfField_T;
+   typedef typename internal::EdgeCornerStencil< LatticeModel_T >::type EdgeCornerStencil_T;
+
+   EqualLevelBorderStreamCorrection( const BlockDataID & pdfFieldId ) : pdfFieldId_( pdfFieldId ) {}
+
+   void prepare( Block * block ) const { helper( block, true  ); }
+   void   apply( Block * block ) const { helper( block, false ); }
+
+protected:
+
+   bool finerNeighborExistsInVicinity( Block * block, stencil::Direction dir ) const;
+   void helper( Block * block, const bool prep ) const;
+
+   BlockDataID pdfFieldId_;
+};
+
+
+
+template< typename LatticeModel_T >
+bool EqualLevelBorderStreamCorrection< LatticeModel_T >::finerNeighborExistsInVicinity( Block * block, stencil::Direction dir ) const
+{
+   Vector3<int> min( -1 );
+   Vector3<int> max(  1 );
+
+   for( uint_t i = 0; i != 3; ++i )
+   {
+      if( stencil::c[i][dir] == -1 ) max[i] = 0;
+      if( stencil::c[i][dir] ==  1 ) min[i] = 0;
+   }
+   if( LatticeModel_T::Stencil::D == uint_t(2) )
+      min[2] = max[2] = 0;
+
+   for( int z = min[2]; z <= max[2]; ++z ) {
+      for( int y = min[1]; y <= max[1]; ++y ) {
+         for( int x = min[0]; x <= max[0]; ++x )
+         {
+            if( x == 0 && y == 0 && z == 0 )
+               continue;
+            if( block->neighborhoodSectionHasSmallerBlocks( blockforest::getBlockNeighborhoodSectionIndex(x,y,z) ) )
+               return true;
+         }
+      }
+   }
+
+   return false;
+}
+
+
+
+template< typename LatticeModel_T >
+void EqualLevelBorderStreamCorrection< LatticeModel_T >::helper( Block * block, const bool prep ) const
+{
+   PdfField_T * pdfField = block->template getData< PdfField_T >( pdfFieldId_ );
+   const CellInterval xyzSize = pdfField->xyzSize();
+
+   for( auto dir = EdgeCornerStencil_T::beginNoCenter(); dir != EdgeCornerStencil_T::end(); ++dir )
+   {
+      if( block->neighborhoodSectionHasEquallySizedBlock( blockforest::getBlockNeighborhoodSectionIndex(*dir) ) &&
+          finerNeighborExistsInVicinity( block, *dir ) )
+      {
+         CellInterval region( xyzSize );
+         region.expand( cell_idx_t(1) );
+         for( uint_t i = 0; i < 3; ++i )
+         {
+            if( stencil::c[i][*dir] == -1 ) region.max()[i] = region.min()[i];
+            else if( stencil::c[i][*dir] == 1 ) region.min()[i] = region.max()[i];
+            else
+            {
+               region.min()[i] += cell_idx_t(1);
+               region.max()[i] -= cell_idx_t(1);
+            }
+         }
+
+         auto invDir = stencil::inverseDir[ *dir ];
+
+         if( prep )
+         {
+            for( auto cell = pdfField->beginSliceXYZ( region ); cell != pdfField->end(); ++cell ) {
+               for( uint_t n = 0; n < PdfField_T::Stencil::d_per_d_length[invDir]; ++n )
+               {
+                  auto d = PdfField_T::Stencil::d_per_d[invDir][n];
+                  auto idx = PdfField_T::Stencil::idx[ d ];
+                  cell[idx] = cell.neighbor( d, idx );
+               }
+            }
+         }
+         else
+         {
+            for( auto cell = pdfField->beginSliceXYZ( region ); cell != pdfField->end(); ++cell ) {
+               for( uint_t n = 0; n < PdfField_T::Stencil::d_per_d_length[invDir]; ++n )
+               {
+                  auto d = PdfField_T::Stencil::d_per_d[invDir][n];
+                  auto idx = PdfField_T::Stencil::idx[ d ];
+                  cell.neighbor( d, idx ) = cell[idx];
+               }
+            }
+         }
+      }
+   }
+}
+
+
+
+} // namespace refinement
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/LinearExplosion.h b/src/lbm/refinement_rebase/LinearExplosion.h
new file mode 100644
index 000000000..c70b7befe
--- /dev/null
+++ b/src/lbm/refinement_rebase/LinearExplosion.h
@@ -0,0 +1,498 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file LinearExplosion.h
+//! \ingroup lbm
+//! \author Florian Schornbaum <florian.schornbaum@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "lbm/field/PdfField.h"
+#include "lbm/lattice_model/NeighborsStencil.h"
+
+#include "blockforest/Block.h"
+#include "blockforest/BlockNeighborhoodSection.h"
+
+#include "core/OpenMP.h"
+#include "core/cell/CellInterval.h"
+#include "core/debug/Debug.h"
+
+#include "stencil/D3Q27.h"
+
+#include <map>
+
+
+
+namespace walberla {
+namespace lbm {
+namespace refinement {
+
+
+
+namespace internal {
+
+// These two functions have been (static) member functions of class LinearExplosion. Sadly, there have been strange compile errors with
+// the IBM compiler -> possible bug in the compiler. As a workaround, these two functions are now two free functions ...
+
+template< typename PdfField_T, typename BoundaryHandling_T, typename CoarseField >
+void fillTemporaryCoarseField( const cell_idx_t y, const cell_idx_t z, const CellInterval & coarse,
+                               const PdfField_T * const pdfField, const BoundaryHandling_T * const boundaryHandling,
+                               const shared_ptr< CoarseField > & tmpField );
+
+template< typename PdfField_T, typename BoundaryHandling_T, typename CoarseField, typename BoolField >
+void linearInterpolation( const cell_idx_t y, const cell_idx_t z, const CellInterval & fine,
+                          PdfField_T * const pdfField, const BoundaryHandling_T * const boundaryHandling,
+                          const shared_ptr< CoarseField > & tmpField, const shared_ptr< BoolField > & boolField );
+
+} // namespace internal
+
+
+
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+class LinearExplosion
+{
+private:
+
+   typedef typename LatticeModel_T::Stencil Stencil;
+
+   typedef PdfField< LatticeModel_T > PdfField_T;
+
+   typedef field::GhostLayerField< typename PdfField_T::value_type, PdfField_T::F_SIZE > CoarseField;
+   typedef field::GhostLayerField< bool, 1 > BoolField;
+
+public:
+
+   LinearExplosion( const BlockDataID & pdfFieldId, const ConstBlockDataID & boundaryHandlingId ) :
+      pdfFieldId_( pdfFieldId ), boundaryHandlingId_( boundaryHandlingId ) {}
+
+   void operator()( Block * block );
+
+protected:
+
+
+
+   BlockDataID pdfFieldId_;
+   ConstBlockDataID boundaryHandlingId_;
+
+   std::map< Cell, std::pair< shared_ptr< CoarseField >, shared_ptr< BoolField > > > tmpFields_;
+};
+
+
+
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void LinearExplosion< LatticeModel_T, BoundaryHandling_T >::operator()( Block * block )
+{
+   PdfField_T * pdfField = block->template getData< PdfField_T >( pdfFieldId_ );
+   const BoundaryHandling_T * boundaryHandling = block->template getData< BoundaryHandling_T >( boundaryHandlingId_ );
+
+   CellInterval fineInterval = pdfField->xyzSize();
+   CellInterval coarseInterval = fineInterval;
+
+   fineInterval.expand( cell_idx_t(4) );
+   coarseInterval.xMax() = ( coarseInterval.xMax() >> 1 );
+   coarseInterval.yMax() = ( coarseInterval.yMax() >> 1 );
+   coarseInterval.zMax() = ( Stencil::D == uint_t(3) ) ? ( coarseInterval.zMax() >> 1 ) : cell_idx_t(0);
+   coarseInterval.expand( cell_idx_t(2) );
+
+   std::vector< CellInterval > coarseIntervals;
+   std::vector< CellInterval >   fineIntervals;
+
+   // determine regions that need to be interpolated
+
+   for( auto d = NeighborsStencil< LatticeModel_T >::type::beginNoCenter(); d != NeighborsStencil< LatticeModel_T >::type::end(); ++d )
+   {
+      if( block->neighborhoodSectionHasLargerBlock( blockforest::getBlockNeighborhoodSectionIndex(*d) ) )
+      {
+         CellInterval coarse = coarseInterval;
+         CellInterval fine = fineInterval;
+
+         for( uint_t i = 0; i < 3; ++i )
+         {
+            if( stencil::c[i][*d] == -1 )
+            {
+               coarse.max()[i] = coarse.min()[i] + cell_idx_t(1);
+               fine.max()[i] = fine.min()[i] + cell_idx_t(3);
+            }
+            else if( stencil::c[i][*d] == 1 )
+            {
+               coarse.min()[i] = coarse.max()[i] - cell_idx_t(1);
+               fine.min()[i] = fine.max()[i] - cell_idx_t(3);
+            }
+            else
+            {
+               WALBERLA_ASSERT_EQUAL( stencil::c[i][*d], 0 );
+               coarse.min()[i] += cell_idx_t(2);
+               coarse.max()[i] -= cell_idx_t(2);
+               fine.min()[i] += cell_idx_t(4);
+               fine.max()[i] -= cell_idx_t(4);
+            }
+         }
+
+         coarseIntervals.push_back( coarse );
+         fineIntervals.push_back( fine );
+      }
+   }
+
+   // it there are no coarse neighbors -> return
+
+   if( coarseIntervals.empty() )
+   {
+      WALBERLA_ASSERT( fineIntervals.empty() );
+      return;
+   }
+
+   // fetch temporary fields from temporary field storage "tmpFields_"
+
+   if( tmpFields_.find( coarseInterval.max() ) == tmpFields_.end() )
+   {
+      tmpFields_[ coarseInterval.max() ] = std::make_pair( make_shared< CoarseField >( coarseInterval.xSize() - uint_t(4), coarseInterval.ySize() - uint_t(4),
+                                                                                       coarseInterval.zSize() - uint_t(4), uint_t(2), pdfField->layout() ),
+                                                           make_shared< BoolField >( coarseInterval.xSize() - uint_t(4), coarseInterval.ySize() - uint_t(4),
+                                                                                     coarseInterval.zSize() - uint_t(4), uint_t(3), pdfField->layout() ) );
+   }
+   auto fields = tmpFields_[ coarseInterval.max() ];
+   auto tmpField = fields.first;
+   auto boolField = fields.second;
+   
+#ifndef NDEBUG
+   std::fill( tmpField->beginWithGhostLayer(), tmpField->end(), std::numeric_limits< typename PdfField_T::value_type >::quiet_NaN() );
+   std::fill( boolField->beginWithGhostLayer(), boolField->end(), false );
+#endif
+
+   // fill temporary coarse field with values (only regions that are required for the interpolation!)
+
+   for( auto coarse = coarseIntervals.begin(); coarse != coarseIntervals.end(); ++coarse )
+   {
+#ifdef _OPENMP
+
+      if( coarse->zSize() >= coarse->ySize() )
+      {
+         int zMin = int_c( coarse->zMin() );
+         int zMax = int_c( coarse->zMax() ) + 1;
+
+         #pragma omp parallel for schedule(static)
+         for( int zi = zMin; zi < zMax; ++zi )
+         {
+            const cell_idx_t z = cell_idx_c(zi);
+            for( cell_idx_t y = coarse->yMin(); y <= coarse->yMax(); ++y )
+               internal::fillTemporaryCoarseField< PdfField_T, BoundaryHandling_T, CoarseField >( y, z, *coarse, pdfField, boundaryHandling, tmpField );
+         }
+      }
+      else
+      {
+         int yMin = int_c( coarse->yMin() );
+         int yMax = int_c( coarse->yMax() ) + 1;
+
+         #pragma omp parallel for schedule(static)
+         for( int yi = yMin; yi < yMax; ++yi )
+         {
+            const cell_idx_t y = cell_idx_c(yi);
+            for( cell_idx_t z = coarse->zMin(); z <= coarse->zMax(); ++z )
+               internal::fillTemporaryCoarseField< PdfField_T, BoundaryHandling_T, CoarseField >( y, z, *coarse, pdfField, boundaryHandling, tmpField );
+         }
+      }
+#else
+      for( cell_idx_t z = coarse->zMin(); z <= coarse->zMax(); ++z ) {
+         for( cell_idx_t y = coarse->yMin(); y <= coarse->yMax(); ++y )
+         {
+            internal::fillTemporaryCoarseField< PdfField_T, BoundaryHandling_T, CoarseField >( y, z, *coarse, pdfField, boundaryHandling, tmpField );
+         }
+      }
+#endif
+
+      CellInterval expanded( *coarse );
+      expanded.expand( cell_idx_t(1) );
+
+      for( auto cell = boolField->beginSliceXYZ( expanded ); cell != boolField->end(); ++cell )
+         *cell = false;
+   }
+
+   for( auto coarse = coarseIntervals.begin(); coarse != coarseIntervals.end(); ++coarse ) {
+      for( auto z = coarse->zMin(); z <= coarse->zMax(); ++z ) {
+         for( auto y = coarse->yMin(); y <= coarse->yMax(); ++y ) {
+            for( auto x = coarse->xMin(); x <= coarse->xMax(); ++x )
+            {
+               if( boundaryHandling->isDomain( cell_idx_t(2) * x, cell_idx_t(2) * y, cell_idx_t(2) * z ) )
+               {
+                  WALBERLA_ASSERT( boundaryHandling->isDomain( cell_idx_t(2) * x + cell_idx_t(1), cell_idx_t(2) * y,                 cell_idx_t(2) * z                 ) );
+                  WALBERLA_ASSERT( boundaryHandling->isDomain( cell_idx_t(2) * x,                 cell_idx_t(2) * y + cell_idx_t(1), cell_idx_t(2) * z                 ) );
+                  WALBERLA_ASSERT( boundaryHandling->isDomain( cell_idx_t(2) * x + cell_idx_t(1), cell_idx_t(2) * y + cell_idx_t(1), cell_idx_t(2) * z                 ) );
+                  if( Stencil::D == uint_t(3) )
+                  {
+                     WALBERLA_ASSERT( boundaryHandling->isDomain( cell_idx_t(2) * x,                 cell_idx_t(2) * y,                 cell_idx_t(2) * z + cell_idx_t(1) ) );
+                     WALBERLA_ASSERT( boundaryHandling->isDomain( cell_idx_t(2) * x + cell_idx_t(1), cell_idx_t(2) * y,                 cell_idx_t(2) * z + cell_idx_t(1) ) );
+                     WALBERLA_ASSERT( boundaryHandling->isDomain( cell_idx_t(2) * x,                 cell_idx_t(2) * y + cell_idx_t(1), cell_idx_t(2) * z + cell_idx_t(1) ) );
+                     WALBERLA_ASSERT( boundaryHandling->isDomain( cell_idx_t(2) * x + cell_idx_t(1), cell_idx_t(2) * y + cell_idx_t(1), cell_idx_t(2) * z + cell_idx_t(1) ) );
+                  }
+                  
+                  boolField->get(x,y,z) = true;
+               }
+               else
+               {
+                  WALBERLA_ASSERT( !boundaryHandling->isDomain( cell_idx_t(2) * x + cell_idx_t(1), cell_idx_t(2) * y,                 cell_idx_t(2) * z                 ) );
+                  WALBERLA_ASSERT( !boundaryHandling->isDomain( cell_idx_t(2) * x,                 cell_idx_t(2) * y + cell_idx_t(1), cell_idx_t(2) * z                 ) );
+                  WALBERLA_ASSERT( !boundaryHandling->isDomain( cell_idx_t(2) * x + cell_idx_t(1), cell_idx_t(2) * y + cell_idx_t(1), cell_idx_t(2) * z                 ) );
+                  if( Stencil::D == uint_t(3) )
+                  {
+                     WALBERLA_ASSERT( !boundaryHandling->isDomain( cell_idx_t(2) * x,                 cell_idx_t(2) * y,                 cell_idx_t(2) * z + cell_idx_t(1) ) );
+                     WALBERLA_ASSERT( !boundaryHandling->isDomain( cell_idx_t(2) * x + cell_idx_t(1), cell_idx_t(2) * y,                 cell_idx_t(2) * z + cell_idx_t(1) ) );
+                     WALBERLA_ASSERT( !boundaryHandling->isDomain( cell_idx_t(2) * x,                 cell_idx_t(2) * y + cell_idx_t(1), cell_idx_t(2) * z + cell_idx_t(1) ) );
+                     WALBERLA_ASSERT( !boundaryHandling->isDomain( cell_idx_t(2) * x + cell_idx_t(1), cell_idx_t(2) * y + cell_idx_t(1), cell_idx_t(2) * z + cell_idx_t(1) ) );
+                  }
+               }
+            }
+         }
+      }
+   }
+
+   // linear interpolation
+
+   for( auto fine = fineIntervals.begin(); fine != fineIntervals.end(); ++fine )
+   {
+      WALBERLA_ASSERT( (fine->xSize() & uint_t(1)) == uint_t(0) );
+      WALBERLA_ASSERT( (fine->ySize() & uint_t(1)) == uint_t(0) );
+      WALBERLA_ASSERT( ( Stencil::D == uint_t(2) && fine->zSize() == uint_t(1) ) || ( Stencil::D == uint_t(3) && (fine->zSize() & uint_t(1)) == uint_t(0) ) );
+      
+#ifdef _OPENMP
+
+      if( fine->zSize() >= fine->ySize() )
+      {
+         int zSize = (Stencil::D == uint_t(3)) ? ( int_c( fine->zSize() ) / 2 ) : 1;
+
+         #pragma omp parallel for schedule(static)
+         for( int zi = 0; zi < zSize; ++zi )
+         {
+            const cell_idx_t z = fine->zMin() + cell_idx_c(zi) * cell_idx_t(2);
+            for( cell_idx_t y = fine->yMin(); y <= fine->yMax(); y += cell_idx_t(2) )
+               internal::linearInterpolation< PdfField_T, BoundaryHandling_T, CoarseField, BoolField >( y, z, *fine, pdfField, boundaryHandling, tmpField, boolField );
+         }
+      }
+      else
+      {
+         int ySize = int_c( fine->ySize() ) / 2;
+
+         #pragma omp parallel for schedule(static)
+         for( int yi = 0; yi < ySize; ++yi )
+         {
+            const cell_idx_t y = fine->yMin() + cell_idx_c(yi) * cell_idx_t(2);
+            for( cell_idx_t z = fine->zMin(); z <= fine->zMax(); z += cell_idx_t(2) )
+               internal::linearInterpolation< PdfField_T, BoundaryHandling_T, CoarseField, BoolField >( y, z, *fine, pdfField, boundaryHandling, tmpField, boolField );
+         }
+      }
+#else
+      for( cell_idx_t z = fine->zMin(); z <= fine->zMax(); z += cell_idx_t(2) ) {
+         for( cell_idx_t y = fine->yMin(); y <= fine->yMax(); y += cell_idx_t(2) )
+         {
+            internal::linearInterpolation< PdfField_T, BoundaryHandling_T, CoarseField, BoolField >( y, z, *fine, pdfField, boundaryHandling, tmpField, boolField );
+         }
+      }
+#endif
+   }
+}
+
+
+
+namespace internal {
+
+template< typename PdfField_T, typename BoundaryHandling_T, typename CoarseField >
+void fillTemporaryCoarseField( const cell_idx_t y, const cell_idx_t z, const CellInterval & coarse,
+                               const PdfField_T * const pdfField, const BoundaryHandling_T * const boundaryHandling,
+                               const shared_ptr< CoarseField > & tmpField )
+{
+   for( cell_idx_t x = coarse.xMin(); x <= coarse.xMax(); ++x ) {
+      for( uint_t f = 0; f < PdfField_T::F_SIZE; ++f )
+      {
+         const auto fx = cell_idx_t(2) * x;
+         const auto fy = cell_idx_t(2) * y;
+         const auto fz = cell_idx_t(2) * z;
+
+         const auto value = pdfField->get( fx, fy, fz, f );
+
+         if( boundaryHandling->isDomain(fx,fy,fz) )
+         {
+            WALBERLA_ASSERT( !math::isnan( value ) );
+         }
+
+         tmpField->get(x,y,z,f) = value;
+
+         /*
+         WALBERLA_ASSERT( !math::isnan( pdfField->get( fx                , fy                , fz                , f ) ) );
+         WALBERLA_ASSERT( !math::isnan( pdfField->get( fx                , fy                , fz + cell_idx_t(1), f ) ) );
+         WALBERLA_ASSERT( !math::isnan( pdfField->get( fx                , fy + cell_idx_t(1), fz                , f ) ) );
+         WALBERLA_ASSERT( !math::isnan( pdfField->get( fx                , fy + cell_idx_t(1), fz + cell_idx_t(1), f ) ) );
+         WALBERLA_ASSERT( !math::isnan( pdfField->get( fx + cell_idx_t(1), fy                , fz                , f ) ) );
+         WALBERLA_ASSERT( !math::isnan( pdfField->get( fx + cell_idx_t(1), fy                , fz + cell_idx_t(1), f ) ) );
+         WALBERLA_ASSERT( !math::isnan( pdfField->get( fx + cell_idx_t(1), fy + cell_idx_t(1), fz                , f ) ) );
+         WALBERLA_ASSERT( !math::isnan( pdfField->get( fx + cell_idx_t(1), fy + cell_idx_t(1), fz + cell_idx_t(1), f ) ) );
+
+         auto value  = pdfField->get( fx                , fy                , fz                , f );
+              value += pdfField->get( fx                , fy                , fz + cell_idx_t(1), f );
+              value += pdfField->get( fx                , fy + cell_idx_t(1), fz                , f );
+              value += pdfField->get( fx                , fy + cell_idx_t(1), fz + cell_idx_t(1), f );
+              value += pdfField->get( fx + cell_idx_t(1), fy                , fz                , f );
+              value += pdfField->get( fx + cell_idx_t(1), fy                , fz + cell_idx_t(1), f );
+              value += pdfField->get( fx + cell_idx_t(1), fy + cell_idx_t(1), fz                , f );
+              value += pdfField->get( fx + cell_idx_t(1), fy + cell_idx_t(1), fz + cell_idx_t(1), f );
+
+         tmpField->get(x,y,z,f) = real_t(0.125) * value;
+         */
+      }
+   }
+}
+
+
+
+template< typename PdfField_T, typename BoundaryHandling_T, typename CoarseField, typename BoolField >
+void linearInterpolation( const cell_idx_t y, const cell_idx_t z, const CellInterval & fine,
+                          PdfField_T * const pdfField, const BoundaryHandling_T * const boundaryHandling,
+                          const shared_ptr< CoarseField > & tmpField, const shared_ptr< BoolField > & boolField )
+{
+   // see: Chen et al., "Grid refinement in lattice Boltzmann methods based on volumetric formulation", 2006
+
+   static const real_t weights[8][3] = { { -0.25, -0.25, -0.25 },
+                                         { -0.25, -0.25,  0.25 },
+                                         { -0.25,  0.25, -0.25 },
+                                         { -0.25,  0.25,  0.25 },
+                                         {  0.25, -0.25, -0.25 },
+                                         {  0.25, -0.25,  0.25 },
+                                         {  0.25,  0.25, -0.25 },
+                                         {  0.25,  0.25,  0.25 } };
+
+   for( cell_idx_t x = fine.xMin(); x < fine.xMax(); x += cell_idx_t(2) )
+   {
+      if( boundaryHandling->isDomain(x,y,z) )
+      {
+         WALBERLA_ASSERT( boundaryHandling->isDomain( x + cell_idx_t(1), y,                 z                 ) );
+         WALBERLA_ASSERT( boundaryHandling->isDomain( x,                 y + cell_idx_t(1), z                 ) );
+         WALBERLA_ASSERT( boundaryHandling->isDomain( x + cell_idx_t(1), y + cell_idx_t(1), z                 ) );
+         if( PdfField_T::Stencil::D == uint_t(3) )
+         {
+            WALBERLA_ASSERT( boundaryHandling->isDomain( x,                 y,                 z + cell_idx_t(1) ) );
+            WALBERLA_ASSERT( boundaryHandling->isDomain( x + cell_idx_t(1), y,                 z + cell_idx_t(1) ) );
+            WALBERLA_ASSERT( boundaryHandling->isDomain( x,                 y + cell_idx_t(1), z + cell_idx_t(1) ) );
+            WALBERLA_ASSERT( boundaryHandling->isDomain( x + cell_idx_t(1), y + cell_idx_t(1), z + cell_idx_t(1) ) );
+         }
+
+         Cell cell( ( ( x + cell_idx_t(4) ) >> 1 ) - cell_idx_t(2),
+                    ( ( y + cell_idx_t(4) ) >> 1 ) - cell_idx_t(2),
+                    (PdfField_T::Stencil::D == uint_t(3)) ? ( ( ( z + cell_idx_t(4) ) >> 1 ) - cell_idx_t(2) ) : cell_idx_t(0) );
+
+         Cell min[3], max[3];
+
+         min[0][0] = cell[0] - cell_idx_t(1); min[0][1] = cell[1]; min[0][2] = cell[2];
+         max[0][0] = cell[0] + cell_idx_t(1); max[0][1] = cell[1]; max[0][2] = cell[2];
+         min[1][0] = cell[0]; min[1][1] = cell[1] - cell_idx_t(1); min[1][2] = cell[2];
+         max[1][0] = cell[0]; max[1][1] = cell[1] + cell_idx_t(1); max[1][2] = cell[2];
+         min[2][0] = cell[0]; min[2][1] = cell[1]; min[2][2] = cell[2] - cell_idx_t(1);
+         max[2][0] = cell[0]; max[2][1] = cell[1]; max[2][2] = cell[2] + cell_idx_t(1);
+
+         for( uint_t f = 0; f < PdfField_T::F_SIZE; ++f )
+         {
+            WALBERLA_ASSERT( !math::isnan( tmpField->get( cell, f ) ) );
+
+            const auto v = tmpField->get( cell, f );
+
+            Vector3< real_t > grad( real_t(0) );
+
+            for( uint_t i = 0; i < PdfField_T::Stencil::D; ++i )
+            {
+
+#define WALBERLA_LBM_REFINEMENT_EXPLOSION_EXCLUDE_EXTRAPOLATION
+#ifdef WALBERLA_LBM_REFINEMENT_EXPLOSION_EXCLUDE_EXTRAPOLATION
+
+               if( boolField->get( max[i] ) && boolField->get( min[i] ) )
+               {
+                  WALBERLA_ASSERT( !math::isnan( tmpField->get( max[i], f ) ) );
+                  WALBERLA_ASSERT( !math::isnan( tmpField->get( min[i], f ) ) );
+
+                  grad[i] = real_t(0.5) * ( tmpField->get( max[i], f ) - tmpField->get( min[i], f ) );
+               }
+
+#else
+               if( boolField->get( max[i] ) )
+               {
+                  WALBERLA_ASSERT( !math::isnan( tmpField->get( max[i], f ) ) );
+                  if( boolField->get( min[i] ) )
+                  {
+                     WALBERLA_ASSERT( !math::isnan( tmpField->get( min[i], f ) ) );
+                     grad[i] = real_t(0.5) * ( tmpField->get( max[i], f ) - tmpField->get( min[i], f ) );
+                  }
+                  else
+                  {
+                     grad[i] = tmpField->get( max[i], f ) - v;
+                  }
+
+               }
+               else if( boolField->get( min[i] ) )
+               {
+                  WALBERLA_ASSERT( !math::isnan( tmpField->get( min[i], f ) ) );
+                  grad[i] = v - tmpField->get( min[i], f );
+               }
+#endif
+#undef WALBERLA_LBM_REFINEMENT_EXPLOSION_EXCLUDE_EXTRAPOLATION
+            }
+
+#define WALBERLA_LBM_REFINEMENT_EXPLOSION_CHEN_CORRECTION
+#ifdef WALBERLA_LBM_REFINEMENT_EXPLOSION_CHEN_CORRECTION
+
+            const auto direction = PdfField_T::Stencil::dir[f];
+            Vector3< real_t > cNorm( stencil::cNorm[0][direction], stencil::cNorm[1][direction], stencil::cNorm[2][direction] );
+
+            grad = grad - cNorm * ( cNorm * grad );
+#endif
+#undef WALBERLA_LBM_REFINEMENT_EXPLOSION_CHEN_CORRECTION
+
+            const auto xx = x + cell_idx_t(1);
+            const auto yy = y + cell_idx_t(1);
+            pdfField->get( x , y , z , f ) = v + ( weights[0][0] * grad[0] + weights[0][1] * grad[1] + weights[0][2] * grad[2] );
+            pdfField->get( x , yy, z , f ) = v + ( weights[2][0] * grad[0] + weights[2][1] * grad[1] + weights[2][2] * grad[2] );
+            pdfField->get( xx, y , z , f ) = v + ( weights[4][0] * grad[0] + weights[4][1] * grad[1] + weights[4][2] * grad[2] );
+            pdfField->get( xx, yy, z , f ) = v + ( weights[6][0] * grad[0] + weights[6][1] * grad[1] + weights[6][2] * grad[2] );
+
+            if( PdfField_T::Stencil::D == uint_t(3) )
+            {
+               const auto zz = z + cell_idx_t(1);
+               pdfField->get( x , y , zz, f ) = v + ( weights[1][0] * grad[0] + weights[1][1] * grad[1] + weights[1][2] * grad[2] );
+               pdfField->get( x , yy, zz, f ) = v + ( weights[3][0] * grad[0] + weights[3][1] * grad[1] + weights[3][2] * grad[2] );
+               pdfField->get( xx, y , zz, f ) = v + ( weights[5][0] * grad[0] + weights[5][1] * grad[1] + weights[5][2] * grad[2] );
+               pdfField->get( xx, yy, zz, f ) = v + ( weights[7][0] * grad[0] + weights[7][1] * grad[1] + weights[7][2] * grad[2] );
+            }
+         }
+      }
+      else
+      {
+         WALBERLA_ASSERT( !boundaryHandling->isDomain( x + cell_idx_t(1), y,                 z                 ) );
+         WALBERLA_ASSERT( !boundaryHandling->isDomain( x,                 y + cell_idx_t(1), z                 ) );
+         WALBERLA_ASSERT( !boundaryHandling->isDomain( x + cell_idx_t(1), y + cell_idx_t(1), z                 ) );
+         if( PdfField_T::Stencil::D == uint_t(3) )
+         {
+            WALBERLA_ASSERT( !boundaryHandling->isDomain( x,                 y,                 z + cell_idx_t(1) ) );
+            WALBERLA_ASSERT( !boundaryHandling->isDomain( x + cell_idx_t(1), y,                 z + cell_idx_t(1) ) );
+            WALBERLA_ASSERT( !boundaryHandling->isDomain( x,                 y + cell_idx_t(1), z + cell_idx_t(1) ) );
+            WALBERLA_ASSERT( !boundaryHandling->isDomain( x + cell_idx_t(1), y + cell_idx_t(1), z + cell_idx_t(1) ) );
+         }
+      }
+   }
+}
+
+} // namespace internal
+
+
+
+} // namespace refinement
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/PdfFieldPackInfo.h b/src/lbm/refinement_rebase/PdfFieldPackInfo.h
new file mode 100644
index 000000000..6fbfb9221
--- /dev/null
+++ b/src/lbm/refinement_rebase/PdfFieldPackInfo.h
@@ -0,0 +1,1452 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file PdfFieldPackInfo.h
+//! \ingroup lbm
+//! \author Florian Schornbaum <florian.schornbaum@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "TimeStepPdfPackInfo.h"
+
+#include "lbm/field/PdfField.h"
+#include "blockforest/BlockNeighborhoodSection.h"
+#include "core/cell/CellInterval.h"
+#include "stencil/Directions.h"
+
+
+namespace walberla {
+namespace lbm {
+namespace refinement {
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+#endif
+class PdfFieldPackInfo : public TimeStepPdfPackInfo
+{
+public:
+
+   typedef PdfField<LatticeModel_T>          PdfField_T;
+   typedef typename LatticeModel_T::Stencil  Stencil;
+
+#ifdef NDEBUG   
+   PdfFieldPackInfo( const BlockDataID & pdfFieldId, const bool _optimizedEqualLevelCommunication = true,
+                     const bool _optimizedForLinearExplosion = true ) :
+      pdfFieldId_( pdfFieldId ), optimizedEqualLevelCommunication_( _optimizedEqualLevelCommunication ),
+      optimizedForLinearExplosion_( _optimizedForLinearExplosion ), equalLevelCells_( equalLevelCells() ) {}
+#else
+   PdfFieldPackInfo( const BlockDataID & pdfFieldId, const ConstBlockDataID & boundaryHandlingId,
+                     const bool _optimizedEqualLevelCommunication = true, const bool _optimizedForLinearExplosion = true ) :
+      pdfFieldId_( pdfFieldId ), boundaryHandlingId_( boundaryHandlingId ),
+      optimizedEqualLevelCommunication_( _optimizedEqualLevelCommunication ), optimizedForLinearExplosion_( _optimizedForLinearExplosion ),
+      equalLevelCells_( equalLevelCells() ) {}
+#endif
+
+   virtual ~PdfFieldPackInfo() {}
+
+   bool optimizedEqualLevelCommunication() const { return optimizedEqualLevelCommunication_; }
+   void optimizeEqualLevelCommunication( const bool value = true ) { optimizedEqualLevelCommunication_ = value; }
+   
+   bool optimizedForLinearExplosion() const { return optimizedForLinearExplosion_; }
+   void optimizeForLinearExplosion( const bool value = true ) { optimizedForLinearExplosion_ = value; }
+   
+   bool constantDataExchange() const { return true; }
+   bool threadsafeReceiving()  const { return true; }
+
+   void       unpackDataEqualLevel( Block * receiver, stencil::Direction dir, mpi::RecvBuffer & buffer );
+   void communicateLocalEqualLevel( const Block * sender, Block * receiver, stencil::Direction dir );
+
+   void       unpackDataCoarseToFine( Block * fineReceiver, const BlockID & coarseSender, stencil::Direction dir, mpi::RecvBuffer & buffer );
+   void communicateLocalCoarseToFine( const Block * coarseSender, Block * fineReceiver, stencil::Direction dir );
+
+   void       unpackDataFineToCoarse( Block * coarseReceiver, const BlockID & fineSender, stencil::Direction dir, mpi::RecvBuffer & buffer );
+   void communicateLocalFineToCoarse( const Block * fineSender, Block * coarseReceiver, stencil::Direction dir );
+
+protected:
+
+   void packDataEqualLevelImpl( const Block * sender, stencil::Direction dir, mpi::SendBuffer & buffer ) const;
+   void packDataCoarseToFineImpl( const Block * coarseSender, const BlockID &   fineReceiver, stencil::Direction dir, mpi::SendBuffer & buffer ) const;
+   void packDataFineToCoarseImpl( const Block *   fineSender, const BlockID & coarseReceiver, stencil::Direction dir, mpi::SendBuffer & buffer ) const;
+
+   ///////////////////////////////////////////////////////////////////////
+   // Helper functions for determining packing/unpacking cell intervals //
+   ///////////////////////////////////////////////////////////////////////
+
+   static inline CellInterval equalLevelPackInterval  ( stencil::Direction dir, const CellInterval & cellBB, const uint_t numberOfLayers );
+   static        CellInterval equalLevelUnpackInterval( stencil::Direction dir, const CellInterval & cellBB, const uint_t numberOfLayers );
+
+   inline bool equalLevelFaceIntervalSplitable( const CellInterval & interval, stencil::Direction dir ) const;
+   inline std::vector< CellInterval > splitEqualLevelFaceInterval( const CellInterval & interval, stencil::Direction dir ) const; // dir: from sender to receiver
+
+   static inline Vector3< cell_idx_t > getNeighborShift( const BlockID & smallBlock, stencil::Direction dir ); // dir: direction from big to small block
+
+   static CellInterval coarseToFinePackInterval  ( stencil::Direction dir, const CellInterval & cellBB, const BlockID & smallBlock );
+   static CellInterval coarseToFineUnpackInterval( stencil::Direction dir, const CellInterval & cellBB, const BlockID & smallBlock );
+
+   static inline  CellInterval fineToCoarsePackInterval  ( stencil::Direction dir, const CellInterval & cellBB );
+   static         CellInterval fineToCoarseUnpackInterval( stencil::Direction dir, const CellInterval & cellBB, const BlockID & smallBlock );
+
+   //////////////////////////////
+   // General helper functions //
+   //////////////////////////////
+
+   static inline uint_t equalLevelCells();
+   
+   static inline bool isFaceDirection( stencil::Direction dir );
+   static inline bool isEdgeDirection( stencil::Direction dir );
+   static inline bool isCornerDirection( stencil::Direction dir );
+
+   static inline bool blocksConnectedByFaces( const Block * block, const BlockID & neighbor );
+   static inline bool blocksConnectedByEdges( const Block * block, const BlockID & neighbor );
+
+   static inline bool divisibleByTwo( const CellInterval & cellBB );
+
+   static bool coarserNeighborExistsInVicinity( const Block * block, stencil::Direction dir );
+
+
+
+   BlockDataID pdfFieldId_;
+#ifndef NDEBUG
+   ConstBlockDataID boundaryHandlingId_;
+#endif
+   
+   bool optimizedEqualLevelCommunication_;
+   bool optimizedForLinearExplosion_;
+   
+   uint_t equalLevelCells_;
+};
+
+
+
+/////////////////
+// Equal level //
+/////////////////
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+void PdfFieldPackInfo< LatticeModel_T >::packDataEqualLevelImpl( const Block * sender, stencil::Direction dir, mpi::SendBuffer & buffer ) const
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::packDataEqualLevelImpl( const Block * sender, stencil::Direction dir,
+                                                                                     mpi::SendBuffer & buffer ) const
+#endif
+{
+#ifndef NDEBUG
+   if( Stencil::D == uint_t(2) )
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+#endif
+
+   const bool optimizedCommunication = ( optimizedEqualLevelCommunication_ && !coarserNeighborExistsInVicinity( sender, dir ) );
+
+   if( optimizedCommunication && Stencil::d_per_d_length[dir] == uint_t(0) )
+      return;
+
+   const PdfField_T * field = sender->getData< PdfField_T >( pdfFieldId_ );
+
+   if( optimizedCommunication )
+   {
+      CellInterval packingInterval = equalLevelPackInterval( dir, field->xyzSize(), uint_t(1) );
+
+      for( auto cell = field->beginSliceXYZ( packingInterval ); cell != field->end(); ++cell )
+         for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d )
+            buffer << cell.getF( Stencil::idx[ Stencil::d_per_d[dir][d] ] );
+
+      /*
+       * --> fzyx (sadly, seems to be slower ... ?)
+       *
+      for( auto z = packingInterval.zMin(); z <= packingInterval.zMax(); ++z ) {
+         for( auto y = packingInterval.yMin(); y <= packingInterval.yMax(); ++y ) {
+            for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d ) {
+
+               auto * s = &( field->get( packingInterval.xMin(), y, z, Stencil::d_per_d[dir][d] ) );
+
+               for( uint_t x = 0; x < packingInterval.xSize(); ++x )
+                  buffer << s[x];
+            }
+         }
+      }
+      */
+   }
+   else
+   {
+      CellInterval packingInterval = equalLevelPackInterval( dir, field->xyzSize(), equalLevelCells_ );
+
+      if( optimizedEqualLevelCommunication_ && isFaceDirection( dir ) && Stencil::D == uint_t(3) )
+      {
+         WALBERLA_ASSERT( equalLevelFaceIntervalSplitable( packingInterval, dir ) );
+
+         std::vector< CellInterval > intervals = splitEqualLevelFaceInterval( packingInterval, dir );
+         WALBERLA_ASSERT_EQUAL( intervals.size(), 5 );
+
+         for( uint_t i = 0; i < 4; ++i )
+         {
+            for( auto cell = field->beginSliceXYZ( intervals[i] ); cell != field->end(); ++cell )
+               for( uint_t d = 0; d < Stencil::Size; ++d )
+                  buffer << cell.getF( d );
+         }
+
+         if( Stencil::d_per_d_length[dir] > uint_t(0) )
+            for( auto cell = field->beginSliceXYZ( intervals[4] ); cell != field->end(); ++cell )
+               for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d )
+                  buffer << cell.getF( Stencil::idx[ Stencil::d_per_d[dir][d] ] );
+      }
+      else
+      {
+         for( auto cell = field->beginSliceXYZ( packingInterval ); cell != field->end(); ++cell )
+            for( uint_t d = 0; d < Stencil::Size; ++d )
+               buffer << cell.getF( d );
+
+         /*
+          * --> fzyx (sadly, seems to be slower ... ?)
+          *
+         for( auto z = packingInterval.zMin(); z <= packingInterval.zMax(); ++z ) {
+            for( auto y = packingInterval.yMin(); y <= packingInterval.yMax(); ++y ) {
+               for( uint_t d = 0; d < Stencil::Size; ++d ) {
+
+                  auto * s = &( field->get( packingInterval.xMin(), y, z, d ) );
+
+                  for( uint_t x = 0; x < packingInterval.xSize(); ++x )
+                     buffer << s[x];
+               }
+            }
+         }
+         */
+      }
+   }
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+void PdfFieldPackInfo< LatticeModel_T >::unpackDataEqualLevel( Block * receiver, stencil::Direction dir, mpi::RecvBuffer & buffer )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::unpackDataEqualLevel( Block * receiver, stencil::Direction dir,
+                                                                                   mpi::RecvBuffer & buffer )
+#endif
+{
+#ifndef NDEBUG
+   if( Stencil::D == uint_t(2) )
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+#endif
+
+   const bool optimizedCommunication = ( optimizedEqualLevelCommunication_ && !coarserNeighborExistsInVicinity( receiver, dir ) );
+
+   const auto invDir = stencil::inverseDir[dir];
+
+   if( optimizedCommunication && Stencil::d_per_d_length[invDir] == uint_t(0) )
+      return;
+
+   PdfField_T * field = receiver->getData< PdfField_T >( pdfFieldId_ );
+
+   if( optimizedCommunication )
+   {
+      CellInterval unpackingInterval = equalLevelUnpackInterval( dir, field->xyzSize(), uint_t(1) );
+
+      for( auto cell = field->beginSliceXYZ( unpackingInterval ); cell != field->end(); ++cell )
+         for( uint_t d = 0; d < Stencil::d_per_d_length[invDir]; ++d )
+            buffer >> cell.getF( Stencil::idx[ Stencil::d_per_d[invDir][d] ] );
+
+      /*
+       * --> fzyx (sadly, seems to be slower ... ?)
+       *
+      for( auto z = unpackingInterval.zMin(); z <= unpackingInterval.zMax(); ++z ) {
+         for( auto y = unpackingInterval.yMin(); y <= unpackingInterval.yMax(); ++y ) {
+            for( uint_t d = 0; d < Stencil::d_per_d_length[invDir]; ++d ) {
+
+               auto * r = &( field->get( unpackingInterval.xMin(), y, z, Stencil::d_per_d[invDir][d] ) );
+
+               for( uint_t x = 0; x < unpackingInterval.xSize(); ++x )
+                  buffer >> r[x];
+            }
+         }
+      }
+      */
+   }
+   else
+   {
+      CellInterval unpackingInterval = equalLevelUnpackInterval( dir, field->xyzSize(), equalLevelCells_ );
+
+      if( optimizedEqualLevelCommunication_ && isFaceDirection( dir ) && Stencil::D == uint_t(3) )
+      {
+         WALBERLA_ASSERT( equalLevelFaceIntervalSplitable( unpackingInterval, invDir ) );
+
+         std::vector< CellInterval > intervals = splitEqualLevelFaceInterval( unpackingInterval, invDir );
+         WALBERLA_ASSERT_EQUAL( intervals.size(), 5 );
+
+         for( uint_t i = 0; i < 4; ++i )
+         {
+            for( auto cell = field->beginSliceXYZ( intervals[i] ); cell != field->end(); ++cell )
+               for( uint_t d = 0; d < Stencil::Size; ++d )
+                  buffer >> cell.getF( d );
+         }
+
+         if( Stencil::d_per_d_length[invDir] > uint_t(0) )
+            for( auto cell = field->beginSliceXYZ( intervals[4] ); cell != field->end(); ++cell )
+               for( uint_t d = 0; d < Stencil::d_per_d_length[invDir]; ++d )
+                  buffer >> cell.getF( Stencil::idx[ Stencil::d_per_d[invDir][d] ] );
+      }
+      else
+      {
+         for( auto cell = field->beginSliceXYZ( unpackingInterval ); cell != field->end(); ++cell )
+            for( uint_t d = 0; d < Stencil::Size; ++d )
+               buffer >> cell.getF( d );
+
+         /*
+          * --> fzyx (sadly, seems to be slower ... ?)
+          *
+         for( auto z = unpackingInterval.zMin(); z <= unpackingInterval.zMax(); ++z ) {
+            for( auto y = unpackingInterval.yMin(); y <= unpackingInterval.yMax(); ++y ) {
+               for( uint_t d = 0; d < Stencil::Size; ++d ) {
+
+                  auto * r = &( field->get( unpackingInterval.xMin(), y, z, d ) );
+
+                  for( uint_t x = 0; x < unpackingInterval.xSize(); ++x )
+                     buffer >> r[x];
+               }
+            }
+         }
+         */
+      }
+   }
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+void PdfFieldPackInfo< LatticeModel_T >::communicateLocalEqualLevel( const Block * sender, Block * receiver, stencil::Direction dir )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::communicateLocalEqualLevel( const Block * sender, Block * receiver, stencil::Direction dir )
+#endif
+{
+#ifndef NDEBUG
+   if( Stencil::D == uint_t(2) )
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+#endif
+
+   const bool optimizedCommunication = ( optimizedEqualLevelCommunication_ && !coarserNeighborExistsInVicinity( sender, dir ) );
+
+   if( optimizedCommunication && Stencil::d_per_d_length[dir] == uint_t(0) )
+      return;
+
+   const PdfField_T * sf =   sender->getData< PdfField_T >( pdfFieldId_ );
+         PdfField_T * rf = receiver->getData< PdfField_T >( pdfFieldId_ );
+
+   WALBERLA_ASSERT_EQUAL( sf->xyzSize(), rf->xyzSize() );
+
+   if( optimizedCommunication )
+   {
+      CellInterval   packingInterval = equalLevelPackInterval( dir, sf->xyzSize(), uint_t(1) );
+      CellInterval unpackingInterval = equalLevelUnpackInterval( stencil::inverseDir[dir], rf->xyzSize(), uint_t(1) );
+
+      WALBERLA_ASSERT_EQUAL( packingInterval.xSize(), unpackingInterval.xSize() );
+      WALBERLA_ASSERT_EQUAL( packingInterval.ySize(), unpackingInterval.ySize() );
+      WALBERLA_ASSERT_EQUAL( packingInterval.zSize(), unpackingInterval.zSize() );
+
+      auto sCell = sf->beginSliceXYZ(   packingInterval );
+      auto rCell = rf->beginSliceXYZ( unpackingInterval );
+      while( sCell != sf->end() )
+      {
+         WALBERLA_ASSERT( rCell != rf->end() );
+
+         for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d )
+         {
+            const auto idx = Stencil::idx[ Stencil::d_per_d[dir][d] ];
+            rCell.getF( idx ) = sCell.getF( idx );
+         }
+
+         ++sCell;
+         ++rCell;
+      }
+      WALBERLA_ASSERT( rCell == rf->end() );
+
+      /*
+       * --> fzyx (sadly, seems to be slower ... ?)
+       *
+      for( auto zp = packingInterval.zMin(), zu = unpackingInterval.zMin(); zp <= packingInterval.zMax(); ++zp, ++zu ) {
+         for( auto yp = packingInterval.yMin(), yu = unpackingInterval.yMin(); yp <= packingInterval.yMax(); ++yp, ++yu ) {
+            for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d ) {
+
+               auto * s = &( sf->get(   packingInterval.xMin(), yp, zp, Stencil::d_per_d[dir][d] ) );
+               auto * r = &( rf->get( unpackingInterval.xMin(), yu, zu, Stencil::d_per_d[dir][d] ) );
+
+               std::copy( s, s+packingInterval.xSize(), r );
+            }
+         }
+      }
+      */
+   }
+   else
+   {
+      CellInterval   packingInterval = equalLevelPackInterval( dir, sf->xyzSize(), equalLevelCells_ );
+      CellInterval unpackingInterval = equalLevelUnpackInterval( stencil::inverseDir[dir], rf->xyzSize(), equalLevelCells_ );
+
+      WALBERLA_ASSERT_EQUAL( packingInterval.xSize(), unpackingInterval.xSize() );
+      WALBERLA_ASSERT_EQUAL( packingInterval.ySize(), unpackingInterval.ySize() );
+      WALBERLA_ASSERT_EQUAL( packingInterval.zSize(), unpackingInterval.zSize() );
+
+      if( optimizedEqualLevelCommunication_ && isFaceDirection( dir ) && Stencil::D == uint_t(3) )
+      {
+         WALBERLA_ASSERT( equalLevelFaceIntervalSplitable(   packingInterval, dir ) );
+         WALBERLA_ASSERT( equalLevelFaceIntervalSplitable( unpackingInterval, dir ) );
+
+         std::vector< CellInterval > pIntervals = splitEqualLevelFaceInterval(   packingInterval, dir );
+         std::vector< CellInterval > uIntervals = splitEqualLevelFaceInterval( unpackingInterval, dir );
+         WALBERLA_ASSERT_EQUAL( pIntervals.size(), 5 );
+         WALBERLA_ASSERT_EQUAL( uIntervals.size(), 5 );
+
+         for( uint_t i = 0; i < 4; ++i )
+         {
+            auto sCell = sf->beginSliceXYZ( pIntervals[i] );
+            auto rCell = rf->beginSliceXYZ( uIntervals[i] );
+            while( sCell != sf->end() )
+            {
+               WALBERLA_ASSERT( rCell != rf->end() );
+
+               for( uint_t d = 0; d < Stencil::Size; ++d )
+                  rCell.getF( d ) = sCell.getF( d );
+
+               ++sCell;
+               ++rCell;
+            }
+            WALBERLA_ASSERT( rCell == rf->end() );
+         }
+
+         if( Stencil::d_per_d_length[dir] > uint_t(0) )
+         {
+            auto sCell = sf->beginSliceXYZ( pIntervals[4] );
+            auto rCell = rf->beginSliceXYZ( uIntervals[4] );
+            while( sCell != sf->end() )
+            {
+               WALBERLA_ASSERT( rCell != rf->end() );
+
+               for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d )
+               {
+                  const auto idx = Stencil::idx[ Stencil::d_per_d[dir][d] ];
+                  rCell.getF( idx ) = sCell.getF( idx );
+               }
+
+               ++sCell;
+               ++rCell;
+            }
+            WALBERLA_ASSERT( rCell == rf->end() );
+         }
+      }
+      else
+      {
+         auto sCell = sf->beginSliceXYZ(   packingInterval );
+         auto rCell = rf->beginSliceXYZ( unpackingInterval );
+         while( sCell != sf->end() )
+         {
+            WALBERLA_ASSERT( rCell != rf->end() );
+
+            for( uint_t d = 0; d < Stencil::Size; ++d )
+               rCell.getF( d ) = sCell.getF( d );
+
+            ++sCell;
+            ++rCell;
+         }
+         WALBERLA_ASSERT( rCell == rf->end() );
+
+         /*
+          * --> fzyx (sadly, seems to be slower ... ?)
+          *
+         for( auto zp = packingInterval.zMin(), zu = unpackingInterval.zMin(); zp <= packingInterval.zMax(); ++zp, ++zu ) {
+            for( auto yp = packingInterval.yMin(), yu = unpackingInterval.yMin(); yp <= packingInterval.yMax(); ++yp, ++yu ) {
+               for( uint_t d = 0; d < Stencil::Size; ++d ) {
+
+                  auto * s = &( sf->get(   packingInterval.xMin(), yp, zp, d ) );
+                  auto * r = &( rf->get( unpackingInterval.xMin(), yu, zu, d ) );
+
+                  std::copy( s, s+packingInterval.xSize(), r );
+               }
+            }
+         }
+         */
+      }
+   }
+}
+
+
+
+////////////////////
+// Coarse to fine //
+////////////////////
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+void PdfFieldPackInfo< LatticeModel_T >::packDataCoarseToFineImpl( const Block * coarseSender, const BlockID & fineReceiver,
+                                                                   stencil::Direction dir, mpi::SendBuffer & buffer ) const
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::packDataCoarseToFineImpl( const Block * coarseSender, const BlockID & fineReceiver,
+                                                                                       stencil::Direction dir, mpi::SendBuffer & buffer ) const
+#endif
+{
+#ifndef NDEBUG
+   if( Stencil::D == uint_t(2) )
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+#endif
+
+   const PdfField_T * field = coarseSender->getData< PdfField_T >( pdfFieldId_ );
+
+#ifndef NDEBUG
+   const BoundaryHandling_T * boundaryHandling = coarseSender->template getData< BoundaryHandling_T >( boundaryHandlingId_ );
+#endif
+
+   CellInterval packingInterval = coarseToFinePackInterval( dir, field->xyzSize(), fineReceiver );
+
+   for( cell_idx_t z = packingInterval.zMin(); z <= packingInterval.zMax(); ++z ) {
+      for( cell_idx_t y = packingInterval.yMin(); y <= packingInterval.yMax(); ++y ) {
+         for( cell_idx_t x = packingInterval.xMin(); x <= packingInterval.xMax(); ++x ) {
+            for( uint_t d = 0; d < Stencil::Size; ++d )
+          //for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d )
+            {
+               buffer << field->get( x, y, z, d );
+             //buffer << field->get( x, y, z, Stencil::idx[ Stencil::d_per_d[dir][d] ] );
+
+#ifndef NDEBUG
+               if( boundaryHandling->isDomain(x,y,z) )
+                  WALBERLA_ASSERT( !math::isnan( field->get( x, y, z, d ) ) );
+#endif
+            }
+         }
+      }
+   }
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+void PdfFieldPackInfo< LatticeModel_T >::unpackDataCoarseToFine( Block * fineReceiver, const BlockID & /*coarseSender*/,
+                                                                 stencil::Direction dir, mpi::RecvBuffer & buffer )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::unpackDataCoarseToFine( Block * fineReceiver, const BlockID & /*coarseSender*/,
+                                                                                     stencil::Direction dir, mpi::RecvBuffer & buffer )
+#endif
+{
+#ifndef NDEBUG
+   if( Stencil::D == uint_t(2) )
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+#endif
+
+   PdfField_T * field = fineReceiver->getData< PdfField_T >( pdfFieldId_ );
+
+   CellInterval unpackingInterval = coarseToFineUnpackInterval( dir, field->xyzSize(), fineReceiver->getId() );
+
+   if( optimizedForLinearExplosion_ )
+   {
+      for( cell_idx_t z = unpackingInterval.zMin(); z <= unpackingInterval.zMax(); z += cell_idx_t(2) ) {
+         for( cell_idx_t y = unpackingInterval.yMin(); y <= unpackingInterval.yMax(); y += cell_idx_t(2) ) {
+            for( cell_idx_t x = unpackingInterval.xMin(); x <= unpackingInterval.xMax(); x += cell_idx_t(2) ) {
+               for( uint_t idx = 0; idx < Stencil::Size; ++idx )
+               {
+                  typename PdfField_T::value_type value;
+                  buffer >> value;
+                  field->get( x, y, z, idx ) = value;
+               }
+            }
+         }
+      }
+   }
+   else
+   {
+      for( cell_idx_t z = unpackingInterval.zMin(); z <= unpackingInterval.zMax(); z += cell_idx_t(2) ) {
+         for( cell_idx_t y = unpackingInterval.yMin(); y <= unpackingInterval.yMax(); y += cell_idx_t(2) ) {
+            for( cell_idx_t x = unpackingInterval.xMin(); x <= unpackingInterval.xMax(); x += cell_idx_t(2) ) {
+               for( uint_t idx = 0; idx < Stencil::Size; ++idx )
+             //for( uint_t d = 0; d < Stencil::d_per_d_length[ stencil::inverseDir[dir] ]; ++d )
+               {
+                  typename PdfField_T::value_type value;
+                  buffer >> value;
+
+                //const auto idx = Stencil::idx[ Stencil::d_per_d[ stencil::inverseDir[dir] ][d] ];
+
+                  field->get( x,                 y,                 z,                 idx ) = value;
+                  field->get( x + cell_idx_t(1), y,                 z,                 idx ) = value;
+                  field->get( x,                 y + cell_idx_t(1), z,                 idx ) = value;
+                  field->get( x + cell_idx_t(1), y + cell_idx_t(1), z,                 idx ) = value;
+                  if( Stencil::D == uint_t(3) )
+                  {
+                     field->get( x,                 y,                 z + cell_idx_t(1), idx ) = value;
+                     field->get( x + cell_idx_t(1), y,                 z + cell_idx_t(1), idx ) = value;
+                     field->get( x,                 y + cell_idx_t(1), z + cell_idx_t(1), idx ) = value;
+                     field->get( x + cell_idx_t(1), y + cell_idx_t(1), z + cell_idx_t(1), idx ) = value;
+                  }
+               }
+            }
+         }
+      }
+   }
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+void PdfFieldPackInfo< LatticeModel_T >::communicateLocalCoarseToFine( const Block * coarseSender, Block * fineReceiver, stencil::Direction dir )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::communicateLocalCoarseToFine( const Block * coarseSender, Block * fineReceiver,
+                                                                                           stencil::Direction dir )
+#endif
+{
+#ifndef NDEBUG
+   if( Stencil::D == uint_t(2) )
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+#endif
+
+   const PdfField_T * sf = coarseSender->getData< PdfField_T >( pdfFieldId_ );
+         PdfField_T * rf = fineReceiver->getData< PdfField_T >( pdfFieldId_ );
+
+#ifndef NDEBUG
+   const BoundaryHandling_T * boundaryHandling = coarseSender->template getData< BoundaryHandling_T >( boundaryHandlingId_ );
+#endif
+
+   WALBERLA_ASSERT_EQUAL( sf->xyzSize(), rf->xyzSize() );
+
+   CellInterval   packingInterval = coarseToFinePackInterval( dir, sf->xyzSize(), fineReceiver->getId() );
+   CellInterval unpackingInterval = coarseToFineUnpackInterval( stencil::inverseDir[dir], rf->xyzSize(), fineReceiver->getId() );
+
+   WALBERLA_ASSERT_EQUAL( packingInterval.xSize() * uint_t(2), unpackingInterval.xSize() );
+   WALBERLA_ASSERT_EQUAL( packingInterval.ySize() * uint_t(2), unpackingInterval.ySize() );
+   if( Stencil::D == uint_t(3) )
+   {
+      WALBERLA_ASSERT_EQUAL( packingInterval.zSize() * uint_t(2), unpackingInterval.zSize() );
+   }
+   else
+   {
+      WALBERLA_ASSERT_EQUAL( packingInterval.zSize(), unpackingInterval.zSize() );
+   }
+
+   if( optimizedForLinearExplosion_ )
+   {
+      cell_idx_t rz = unpackingInterval.zMin();
+      for( cell_idx_t sz = packingInterval.zMin(); sz <= packingInterval.zMax(); ++sz )
+      {
+         cell_idx_t ry = unpackingInterval.yMin();
+         for( cell_idx_t sy = packingInterval.yMin(); sy <= packingInterval.yMax(); ++sy )
+         {
+            cell_idx_t rx = unpackingInterval.xMin();
+            for( cell_idx_t sx = packingInterval.xMin(); sx <= packingInterval.xMax(); ++sx ) {
+               for( uint_t idx = 0; idx < Stencil::Size; ++idx )
+               {
+                  rf->get( rx, ry, rz, idx ) = sf->get( sx, sy, sz, idx );
+
+#ifndef NDEBUG
+               if( boundaryHandling->isDomain(sx,sy,sz) )
+                  WALBERLA_ASSERT( !math::isnan( sf->get( sx, sy, sz, idx ) ), sx << ", " << sy << ", " << sz << ", " << idx << " coarse sender block = " << coarseSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> " );
+#endif
+               }
+               rx += cell_idx_t(2);
+            }
+            ry += cell_idx_t(2);
+            WALBERLA_ASSERT_EQUAL( rx, unpackingInterval.xMax() + cell_idx_t(1) );
+         }
+         rz += cell_idx_t(2);
+         WALBERLA_ASSERT_EQUAL( ry, unpackingInterval.yMax() + cell_idx_t(1) );
+      }
+      if( Stencil::D == uint_t(3) )
+      {
+         WALBERLA_ASSERT_EQUAL( rz, unpackingInterval.zMax() + cell_idx_t(1) );
+      }
+      else
+      {
+         WALBERLA_ASSERT_EQUAL( rz, unpackingInterval.zMax() + cell_idx_t(2) );
+      }
+   }
+   else
+   {
+      cell_idx_t rz = unpackingInterval.zMin();
+      for( cell_idx_t sz = packingInterval.zMin(); sz <= packingInterval.zMax(); ++sz )
+      {
+         cell_idx_t ry = unpackingInterval.yMin();
+         for( cell_idx_t sy = packingInterval.yMin(); sy <= packingInterval.yMax(); ++sy )
+         {
+            cell_idx_t rx = unpackingInterval.xMin();
+            for( cell_idx_t sx = packingInterval.xMin(); sx <= packingInterval.xMax(); ++sx ) {
+               for( uint_t idx = 0; idx < Stencil::Size; ++idx )
+             //for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d )
+               {
+                //const auto idx = Stencil::idx[ Stencil::d_per_d[dir][d] ];
+
+                  const auto & value = sf->get(sx,sy,sz,idx);
+
+#ifndef NDEBUG
+               if( boundaryHandling->isDomain(sx,sy,sz) )
+                  WALBERLA_ASSERT( !math::isnan( value ), "value at " << sx << ", " << sy << ", " << sz << ", " << idx << " coarse sender block = " << coarseSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> " );
+#endif
+
+                  rf->get( rx,                 ry,                 rz,                 idx ) = value;
+                  rf->get( rx + cell_idx_t(1), ry,                 rz,                 idx ) = value;
+                  rf->get( rx,                 ry + cell_idx_t(1), rz,                 idx ) = value;
+                  rf->get( rx + cell_idx_t(1), ry + cell_idx_t(1), rz,                 idx ) = value;
+                  if( Stencil::D == uint_t(3) )
+                  {
+                     rf->get( rx,                 ry,                 rz + cell_idx_t(1), idx ) = value;
+                     rf->get( rx + cell_idx_t(1), ry,                 rz + cell_idx_t(1), idx ) = value;
+                     rf->get( rx,                 ry + cell_idx_t(1), rz + cell_idx_t(1), idx ) = value;
+                     rf->get( rx + cell_idx_t(1), ry + cell_idx_t(1), rz + cell_idx_t(1), idx ) = value;
+                  }
+               }
+               rx += cell_idx_t(2);
+            }
+            ry += cell_idx_t(2);
+            WALBERLA_ASSERT_EQUAL( rx, unpackingInterval.xMax() + cell_idx_t(1) );
+         }
+         rz += cell_idx_t(2);
+         WALBERLA_ASSERT_EQUAL( ry, unpackingInterval.yMax() + cell_idx_t(1) );
+      }
+      if( Stencil::D == uint_t(3) )
+      {
+         WALBERLA_ASSERT_EQUAL( rz, unpackingInterval.zMax() + cell_idx_t(1) );
+      }
+      else
+      {
+         WALBERLA_ASSERT_EQUAL( rz, unpackingInterval.zMax() + cell_idx_t(2) );
+      }
+   }
+}
+
+
+
+////////////////////
+// Fine to coarse //
+////////////////////
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+void PdfFieldPackInfo< LatticeModel_T >::packDataFineToCoarseImpl( const Block * fineSender, const BlockID & coarseReceiver,
+                                                                   stencil::Direction dir, mpi::SendBuffer & buffer ) const
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::packDataFineToCoarseImpl( const Block * fineSender, const BlockID & coarseReceiver,
+                                                                                       stencil::Direction dir, mpi::SendBuffer & buffer ) const
+#endif
+{
+#ifndef NDEBUG
+   if( Stencil::D == uint_t(2) )
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+#endif
+
+   if( Stencil::d_per_d_length[dir] == uint_t(0) )
+      return;
+
+   if( ( ( isEdgeDirection(dir) || isCornerDirection(dir) ) && blocksConnectedByFaces( fineSender, coarseReceiver ) ) ||
+       ( isCornerDirection(dir) && blocksConnectedByEdges( fineSender, coarseReceiver ) ) )
+      return;
+
+   const PdfField_T * field = fineSender->getData< PdfField_T >( pdfFieldId_ );
+   
+#ifndef NDEBUG
+   const BoundaryHandling_T * boundaryHandling = fineSender->template getData< BoundaryHandling_T >( boundaryHandlingId_ );
+#endif   
+
+   CellInterval packingInterval = fineToCoarsePackInterval( dir, field->xyzSize() );
+
+   const real_t factor = ( Stencil::D == uint_t(3) ) ? real_t( 0.125 ) : real_t( 0.25 );
+
+   for( cell_idx_t z = packingInterval.zMin(); z <= packingInterval.zMax(); z += cell_idx_t(2) ) {
+      for( cell_idx_t y = packingInterval.yMin(); y <= packingInterval.yMax(); y += cell_idx_t(2) ) {
+         for( cell_idx_t x = packingInterval.xMin(); x <= packingInterval.xMax(); x += cell_idx_t(2) ) {
+            for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d )
+            {
+               const auto idx = Stencil::idx[ Stencil::d_per_d[dir][d] ];
+
+#ifndef NDEBUG
+               if( boundaryHandling->isDomain(x,y,z) )
+               {
+                  WALBERLA_ASSERT( !math::isnan( field->get( x,                 y,                 z,                 idx ) ), x << ", " << y << ", " << z << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> ");
+                  WALBERLA_ASSERT( !math::isnan( field->get( x + cell_idx_t(1), y,                 z,                 idx ) ), x + cell_idx_t(1) << ", " << y << ", " << z << ", " << idx <<" fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> " );
+                  WALBERLA_ASSERT( !math::isnan( field->get( x,                 y + cell_idx_t(1), z,                 idx ) ), x << ", " << y + cell_idx_t(1) << ", " << z << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> " );
+                  WALBERLA_ASSERT( !math::isnan( field->get( x + cell_idx_t(1), y + cell_idx_t(1), z,                 idx ) ), x + cell_idx_t(1) << ", " << y + cell_idx_t(1) << ", " << z << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> " );
+                  if( Stencil::D == uint_t(3) )
+                  {
+                     WALBERLA_ASSERT( !math::isnan( field->get( x,                 y,                 z + cell_idx_t(1), idx ) ), x << ", " << y << ", " << z + cell_idx_t(1) << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> "  );
+                     WALBERLA_ASSERT( !math::isnan( field->get( x + cell_idx_t(1), y,                 z + cell_idx_t(1), idx ) ), x + cell_idx_t(1) << ", " << y << ", " << z + cell_idx_t(1) << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> " );
+                     WALBERLA_ASSERT( !math::isnan( field->get( x,                 y + cell_idx_t(1), z + cell_idx_t(1), idx ) ), x << ", " << y + cell_idx_t(1) << ", " << z + cell_idx_t(1) << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> " );
+                     WALBERLA_ASSERT( !math::isnan( field->get( x + cell_idx_t(1), y + cell_idx_t(1), z + cell_idx_t(1), idx ) ), x + cell_idx_t(1) << ", " << y + cell_idx_t(1) << ", " << z + cell_idx_t(1) << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> " );
+                  }
+               }
+#endif
+
+               typename PdfField_T::value_type value  = field->get( x,                 y,                 z,                 idx );
+                                               value += field->get( x + cell_idx_t(1), y,                 z,                 idx );
+                                               value += field->get( x,                 y + cell_idx_t(1), z,                 idx );
+                                               value += field->get( x + cell_idx_t(1), y + cell_idx_t(1), z,                 idx );
+               if( Stencil::D == uint_t(3) )
+               {
+                                               value += field->get( x,                 y,                 z + cell_idx_t(1), idx );
+                                               value += field->get( x + cell_idx_t(1), y,                 z + cell_idx_t(1), idx );
+                                               value += field->get( x,                 y + cell_idx_t(1), z + cell_idx_t(1), idx );
+                                               value += field->get( x + cell_idx_t(1), y + cell_idx_t(1), z + cell_idx_t(1), idx );
+               }
+
+               buffer << ( factor * value );
+            }
+         }
+      }
+   }
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+void PdfFieldPackInfo< LatticeModel_T >::unpackDataFineToCoarse( Block * coarseReceiver, const BlockID & fineSender,
+                                                                 stencil::Direction dir, mpi::RecvBuffer & buffer )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::unpackDataFineToCoarse( Block * coarseReceiver, const BlockID & fineSender,
+                                                                                     stencil::Direction dir, mpi::RecvBuffer & buffer )
+#endif
+{
+#ifndef NDEBUG
+   if( Stencil::D == uint_t(2) )
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+#endif
+
+   auto invDir = stencil::inverseDir[dir];
+   if( Stencil::d_per_d_length[invDir] == uint_t(0) )
+      return;
+
+   if( ( ( isEdgeDirection(dir) || isCornerDirection(dir) ) && blocksConnectedByFaces( coarseReceiver, fineSender ) ) ||
+       ( isCornerDirection(dir) && blocksConnectedByEdges( coarseReceiver, fineSender ) ) )
+      return;
+
+   PdfField_T * field = coarseReceiver->getData< PdfField_T >( pdfFieldId_ );
+
+#ifndef NDEBUG
+   const BoundaryHandling_T * boundaryHandling = coarseReceiver->template getData< BoundaryHandling_T >( boundaryHandlingId_ );
+#endif
+
+   CellInterval unpackingInterval = fineToCoarseUnpackInterval( dir, field->xyzSize(), fineSender );
+
+   for( cell_idx_t z = unpackingInterval.zMin(); z <= unpackingInterval.zMax(); ++z ) {
+      for( cell_idx_t y = unpackingInterval.yMin(); y <= unpackingInterval.yMax(); ++y ) {
+         for( cell_idx_t x = unpackingInterval.xMin(); x <= unpackingInterval.xMax(); ++x ) {
+            for( uint_t d = 0; d < Stencil::d_per_d_length[invDir]; ++d )
+            {
+               buffer >> field->get( x, y, z, Stencil::idx[ Stencil::d_per_d[invDir][d] ] );
+
+#ifndef NDEBUG
+               if( boundaryHandling->isDomain(x,y,z) )
+                  WALBERLA_ASSERT( !math::isnan( field->get( x, y, z, Stencil::idx[ Stencil::d_per_d[invDir][d] ] ) ) );
+#endif
+            }
+         }
+      }
+   }
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+void PdfFieldPackInfo< LatticeModel_T >::communicateLocalFineToCoarse( const Block * fineSender, Block * coarseReceiver, stencil::Direction dir )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+void PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::communicateLocalFineToCoarse( const Block * fineSender, Block * coarseReceiver,
+                                                                                           stencil::Direction dir )
+#endif
+{
+#ifndef NDEBUG
+   if( Stencil::D == uint_t(2) )
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+#endif
+
+   if( Stencil::d_per_d_length[dir] == uint_t(0) )
+      return;
+
+   if( ( ( isEdgeDirection(dir) || isCornerDirection(dir) ) && blocksConnectedByFaces( fineSender, coarseReceiver->getId() ) ) ||
+       ( isCornerDirection(dir) && blocksConnectedByEdges( fineSender, coarseReceiver->getId() ) ) )
+      return;
+
+   const PdfField_T * sf =     fineSender->getData< PdfField_T >( pdfFieldId_ );
+         PdfField_T * rf = coarseReceiver->getData< PdfField_T >( pdfFieldId_ );
+         
+#ifndef NDEBUG
+   const BoundaryHandling_T * boundaryHandling = fineSender->template getData< BoundaryHandling_T >( boundaryHandlingId_ );
+#endif
+
+   WALBERLA_ASSERT_EQUAL( sf->xyzSize(), rf->xyzSize() );
+
+   CellInterval   packingInterval = fineToCoarsePackInterval( dir, sf->xyzSize() );
+   CellInterval unpackingInterval = fineToCoarseUnpackInterval( stencil::inverseDir[dir], rf->xyzSize(), fineSender->getId() );
+
+   WALBERLA_ASSERT_EQUAL( packingInterval.xSize(), unpackingInterval.xSize() * uint_t(2) );
+   WALBERLA_ASSERT_EQUAL( packingInterval.ySize(), unpackingInterval.ySize() * uint_t(2) );
+   if( Stencil::D == uint_t(3) )
+   {
+      WALBERLA_ASSERT_EQUAL( packingInterval.zSize(), unpackingInterval.zSize() * uint_t(2) );
+   }
+   else
+   {
+      WALBERLA_ASSERT_EQUAL( packingInterval.zSize(), unpackingInterval.zSize() );
+   }
+
+   const real_t factor = ( Stencil::D == uint_t(3) ) ? real_t( 0.125 ) : real_t( 0.25 );
+
+   cell_idx_t sz = packingInterval.zMin();
+   for( cell_idx_t rz = unpackingInterval.zMin(); rz <= unpackingInterval.zMax(); ++rz )
+   {
+      cell_idx_t sy = packingInterval.yMin();
+      for( cell_idx_t ry = unpackingInterval.yMin(); ry <= unpackingInterval.yMax(); ++ry )
+      {
+         cell_idx_t sx = packingInterval.xMin();
+         for( cell_idx_t rx = unpackingInterval.xMin(); rx <= unpackingInterval.xMax(); ++rx ) {
+            for( uint_t d = 0; d < Stencil::d_per_d_length[dir]; ++d )
+            {
+               const auto idx = Stencil::idx[ Stencil::d_per_d[dir][d] ];
+
+#ifndef NDEBUG
+               if( boundaryHandling->isDomain(sx,sy,sz) )
+               {
+                  WALBERLA_ASSERT( !math::isnan( sf->get( sx,                 sy,                 sz,                 idx ) ), sx << ", " << sy << ", " << sz << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> ");
+                  WALBERLA_ASSERT( !math::isnan( sf->get( sx + cell_idx_t(1), sy,                 sz,                 idx ) ), sx + cell_idx_t(1) << ", " << sy << ", " << sz << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> ");
+                  WALBERLA_ASSERT( !math::isnan( sf->get( sx,                 sy + cell_idx_t(1), sz,                 idx ) ), sx << ", " << sy + cell_idx_t(1) << ", " << sz << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> ");
+                  WALBERLA_ASSERT( !math::isnan( sf->get( sx + cell_idx_t(1), sy + cell_idx_t(1), sz,                 idx ) ), sx + cell_idx_t(1) << ", " << sy + cell_idx_t(1) << ", " << sz << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> ");
+                  if( Stencil::D == uint_t(3) )
+                  {
+                     WALBERLA_ASSERT( !math::isnan( sf->get( sx,                 sy,                 sz + cell_idx_t(1), idx ) ), sx << ", " << sy << ", " << sz + cell_idx_t(1) << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> ");
+                     WALBERLA_ASSERT( !math::isnan( sf->get( sx + cell_idx_t(1), sy,                 sz + cell_idx_t(1), idx ) ), sx + cell_idx_t(1) << ", " << sy << ", " << sz + cell_idx_t(1) << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> ");
+                     WALBERLA_ASSERT( !math::isnan( sf->get( sx,                 sy + cell_idx_t(1), sz + cell_idx_t(1), idx ) ), sx << ", " << sy + cell_idx_t(1) << ", " << sz + cell_idx_t(1) << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> ");
+                     WALBERLA_ASSERT( !math::isnan( sf->get( sx + cell_idx_t(1), sy + cell_idx_t(1), sz + cell_idx_t(1), idx ) ), sx + cell_idx_t(1) << ", " << sy + cell_idx_t(1) << ", " << sz + cell_idx_t(1) << ", " << idx << " fine sender block = " << fineSender->getId() << " in dir <" << stencil::cx[dir] << "," << stencil::cy[dir] << "," << stencil::cz[dir] << "> ");
+                  }
+               }
+#endif
+
+               typename PdfField_T::value_type value  = sf->get( sx,                 sy,                 sz,                 idx );
+                                               value += sf->get( sx + cell_idx_t(1), sy,                 sz,                 idx );
+                                               value += sf->get( sx,                 sy + cell_idx_t(1), sz,                 idx );
+                                               value += sf->get( sx + cell_idx_t(1), sy + cell_idx_t(1), sz,                 idx );
+               if( Stencil::D == uint_t(3) )
+               {
+                                               value += sf->get( sx,                 sy,                 sz + cell_idx_t(1), idx );
+                                               value += sf->get( sx + cell_idx_t(1), sy,                 sz + cell_idx_t(1), idx );
+                                               value += sf->get( sx,                 sy + cell_idx_t(1), sz + cell_idx_t(1), idx );
+                                               value += sf->get( sx + cell_idx_t(1), sy + cell_idx_t(1), sz + cell_idx_t(1), idx );
+               }
+
+               rf->get( rx, ry, rz, idx ) = factor * value;
+            }
+            sx += cell_idx_t(2);
+         }
+         sy += cell_idx_t(2);
+         WALBERLA_ASSERT_EQUAL( sx, packingInterval.xMax() + cell_idx_t(1) );
+      }
+      sz += cell_idx_t(2);
+      WALBERLA_ASSERT_EQUAL( sy, packingInterval.yMax() + cell_idx_t(1) );
+   }
+   if( Stencil::D == uint_t(3) )
+   {
+      WALBERLA_ASSERT_EQUAL( sz, packingInterval.zMax() + cell_idx_t(1) );
+   }
+   else
+   {
+      WALBERLA_ASSERT_EQUAL( sz, packingInterval.zMax() + cell_idx_t(2) );
+   }
+}
+
+
+
+///////////////////////////////////////////////////////////////////////
+// Helper functions for determining packing/unpacking cell intervals //
+///////////////////////////////////////////////////////////////////////
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline CellInterval PdfFieldPackInfo< LatticeModel_T >::equalLevelPackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                                const uint_t numberOfLayers )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline CellInterval PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::equalLevelPackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                                                    const uint_t numberOfLayers )
+#endif
+{
+   CellInterval interval = equalLevelUnpackInterval( dir, cellBB, numberOfLayers );
+
+   for( uint_t i = 0; i != Stencil::D; ++i )
+   {
+      const auto offset = cell_idx_c( stencil::c[i][dir] ) * cell_idx_c( numberOfLayers );
+      interval.min()[i] -= offset;
+      interval.max()[i] -= offset;
+   }
+
+   return interval;
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+CellInterval PdfFieldPackInfo< LatticeModel_T >::equalLevelUnpackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                           const uint_t numberOfLayers )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+CellInterval PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::equalLevelUnpackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                                               const uint_t numberOfLayers )
+#endif
+{
+   CellInterval interval( cellBB );
+   interval.expand( cell_idx_c(numberOfLayers) );
+
+   for( uint_t i = 0; i != 3; ++i )
+   {
+      const auto c = cell_idx_c( stencil::c[i][dir] );
+
+      if( c == -1 ) interval.max()[i] = interval.min()[i] + cell_idx_c( numberOfLayers - 1 );
+      else if( c == 1 ) interval.min()[i] = interval.max()[i] - cell_idx_c( numberOfLayers - 1 );
+      else
+      {
+         WALBERLA_ASSERT_EQUAL( c, cell_idx_t(0) );
+         interval.min()[i] += cell_idx_c( numberOfLayers );
+         interval.max()[i] -= cell_idx_c( numberOfLayers );
+      }
+   }
+
+   return interval;
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline bool PdfFieldPackInfo< LatticeModel_T >::equalLevelFaceIntervalSplitable( const CellInterval & interval, stencil::Direction dir ) const
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline bool PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::equalLevelFaceIntervalSplitable( const CellInterval & interval, stencil::Direction dir ) const
+#endif
+{
+   if( stencil::cx[dir] != 0 )
+   {
+      WALBERLA_ASSERT_EQUAL( stencil::cy[dir], 0 );
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+
+      WALBERLA_ASSERT_EQUAL( interval.xSize(), equalLevelCells_ );
+
+      return interval.ySize() > uint_t(2) && interval.zSize() > uint_t(2);
+   }
+   else if( stencil::cy[dir] != 0 )
+   {
+      WALBERLA_ASSERT_EQUAL( stencil::cx[dir], 0 );
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+
+      WALBERLA_ASSERT_EQUAL( interval.ySize(), equalLevelCells_ );
+
+      return interval.xSize() > uint_t(2) && interval.zSize() > uint_t(2);
+   }
+
+   WALBERLA_ASSERT_UNEQUAL( stencil::cz[dir], 0 );
+   WALBERLA_ASSERT_EQUAL( stencil::cx[dir], 0 );
+   WALBERLA_ASSERT_EQUAL( stencil::cy[dir], 0 );
+
+   WALBERLA_ASSERT_EQUAL( interval.zSize(), equalLevelCells_ );
+
+   return interval.xSize() > uint_t(2) && interval.ySize() > uint_t(2);
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline std::vector< CellInterval > PdfFieldPackInfo< LatticeModel_T >::splitEqualLevelFaceInterval( const CellInterval & interval,
+                                                                                                    stencil::Direction dir ) const // dir: from sender to receiver
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline std::vector< CellInterval > PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::splitEqualLevelFaceInterval( const CellInterval & interval,
+                                                                                                                        stencil::Direction dir ) const // dir: from sender to receiver
+#endif
+{
+   std::vector< CellInterval > intervals;
+
+   if( stencil::cx[dir] != 0 )
+   {
+      WALBERLA_ASSERT_EQUAL( stencil::cy[dir], 0 );
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+
+      WALBERLA_ASSERT_EQUAL( interval.xSize(), equalLevelCells_ );
+
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMin(), interval.zMax(),
+                                         interval.xMax(), interval.yMax(), interval.zMax() ) );
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMin(), interval.zMin() + cell_idx_t(1),
+                                         interval.xMax(), interval.yMin(), interval.zMax() - cell_idx_t(1) ) );
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMax(), interval.zMin() + cell_idx_t(1),
+                                         interval.xMax(), interval.yMax(), interval.zMax() - cell_idx_t(1) ) );
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMin(), interval.zMin(),
+                                         interval.xMax(), interval.yMax(), interval.zMin() ) );
+
+      const cell_idx_t x = ( stencil::cx[dir] > 0 ) ? interval.xMax() : interval.xMin();
+      intervals.push_back( CellInterval( x, interval.yMin() + cell_idx_t(1), interval.zMin() + cell_idx_t(1),
+                                         x, interval.yMax() - cell_idx_t(1), interval.zMax() - cell_idx_t(1) ) );
+   }
+   else if( stencil::cy[dir] != 0 )
+   {
+      WALBERLA_ASSERT_EQUAL( stencil::cx[dir], 0 );
+      WALBERLA_ASSERT_EQUAL( stencil::cz[dir], 0 );
+
+      WALBERLA_ASSERT_EQUAL( interval.ySize(), equalLevelCells_ );
+
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMin(), interval.zMax(),
+                                         interval.xMax(), interval.yMax(), interval.zMax() ) );
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMin(), interval.zMin() + cell_idx_t(1),
+                                         interval.xMin(), interval.yMax(), interval.zMax() - cell_idx_t(1) ) );
+      intervals.push_back( CellInterval( interval.xMax(), interval.yMin(), interval.zMin() + cell_idx_t(1),
+                                         interval.xMax(), interval.yMax(), interval.zMax() - cell_idx_t(1) ) );
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMin(), interval.zMin(),
+                                         interval.xMax(), interval.yMax(), interval.zMin() ) );
+
+      const cell_idx_t y = ( stencil::cy[dir] > 0 ) ? interval.yMax() : interval.yMin();
+      intervals.push_back( CellInterval( interval.xMin() + cell_idx_t(1), y, interval.zMin() + cell_idx_t(1),
+                                         interval.xMax() - cell_idx_t(1), y, interval.zMax() - cell_idx_t(1) ) );
+   }
+   else
+   {
+      WALBERLA_ASSERT_UNEQUAL( stencil::cz[dir], 0 );
+      WALBERLA_ASSERT_EQUAL( stencil::cx[dir], 0 );
+      WALBERLA_ASSERT_EQUAL( stencil::cy[dir], 0 );
+
+      WALBERLA_ASSERT_EQUAL( interval.zSize(), equalLevelCells_ );
+
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMax(),                 interval.zMin(),
+                                         interval.xMax(), interval.yMax(),                 interval.zMax() ) );
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMin() + cell_idx_t(1), interval.zMin(),
+                                         interval.xMin(), interval.yMax() - cell_idx_t(1), interval.zMax() ) );
+      intervals.push_back( CellInterval( interval.xMax(), interval.yMin() + cell_idx_t(1), interval.zMin(),
+                                         interval.xMax(), interval.yMax() - cell_idx_t(1), interval.zMax() ) );
+      intervals.push_back( CellInterval( interval.xMin(), interval.yMin(),                 interval.zMin(),
+                                         interval.xMax(), interval.yMin(),                 interval.zMax() ) );
+
+      const cell_idx_t z = ( stencil::cz[dir] > 0 ) ? interval.zMax() : interval.zMin();
+      intervals.push_back( CellInterval( interval.xMin() + cell_idx_t(1), interval.yMin() + cell_idx_t(1), z,
+                                         interval.xMax() - cell_idx_t(1), interval.yMax() - cell_idx_t(1), z ) );
+   }
+
+   return intervals;
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline Vector3< cell_idx_t > PdfFieldPackInfo< LatticeModel_T >::getNeighborShift( const BlockID & smallBlock, stencil::Direction dir ) // dir: direction from big to small block
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline Vector3< cell_idx_t > PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::getNeighborShift( const BlockID & smallBlock,
+                                                                                                       stencil::Direction dir ) // dir: direction from big to small block
+#endif
+{
+   Vector3< cell_idx_t > shift;
+
+   uint_t branchId = smallBlock.getBranchId();
+
+   shift[0] = ( stencil::cx[dir] == 0 ) ? ( ( ( branchId & uint_t(1) ) == uint_t(0) ) ? cell_idx_t(-1) : cell_idx_t(1) ) : cell_idx_t(0);
+   shift[1] = ( stencil::cy[dir] == 0 ) ? ( ( ( branchId & uint_t(2) ) == uint_t(0) ) ? cell_idx_t(-1) : cell_idx_t(1) ) : cell_idx_t(0);
+   shift[2] = ( Stencil::D == uint_t(3) ) ?
+            ( ( stencil::cz[dir] == 0 ) ? ( ( ( branchId & uint_t(4) ) == uint_t(0) ) ? cell_idx_t(-1) : cell_idx_t(1) ) : cell_idx_t(0) ) : cell_idx_t(0);
+
+   return shift;
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+CellInterval PdfFieldPackInfo< LatticeModel_T >::coarseToFinePackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                           const BlockID & smallBlock )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+CellInterval PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::coarseToFinePackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                                               const BlockID & smallBlock )
+#endif
+{
+   CellInterval interval = equalLevelPackInterval( dir, cellBB, uint_t(2) );
+ //CellInterval interval = equalLevelPackInterval( dir, cellBB, uint_t(1) );
+
+   Vector3< cell_idx_t > shift = getNeighborShift( smallBlock, dir );
+
+   WALBERLA_ASSERT( divisibleByTwo( cellBB ) );
+
+   for( uint_t i = 0; i != Stencil::D; ++i )
+   {
+      if( shift[i] == cell_idx_t(-1) )
+         interval.max()[i] = interval.min()[i] + cell_idx_c( cellBB.size(i) / uint_t(2) ) + cell_idx_t(1);
+       //interval.max()[i] = interval.min()[i] + cell_idx_c( cellBB.size(i) / uint_t(2) );
+      if( shift[i] == cell_idx_t( 1) )
+         interval.min()[i] = interval.max()[i] - cell_idx_c( cellBB.size(i) / uint_t(2) ) - cell_idx_t(1);
+       //interval.min()[i] = interval.max()[i] - cell_idx_c( cellBB.size(i) / uint_t(2) );
+   }
+
+   WALBERLA_ASSERT( cellBB.contains( interval ) );
+
+   return interval;
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+CellInterval PdfFieldPackInfo< LatticeModel_T >::coarseToFineUnpackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                             const BlockID & smallBlock )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+CellInterval PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::coarseToFineUnpackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                                                 const BlockID & smallBlock )
+#endif
+{
+   CellInterval interval = equalLevelUnpackInterval( dir, cellBB, uint_t(4) );
+ //CellInterval interval = equalLevelUnpackInterval( dir, cellBB, uint_t(2) );
+
+   Vector3< cell_idx_t > shift = getNeighborShift( smallBlock, dir );
+
+   for( uint_t i = 0; i != Stencil::D; ++i )
+   {
+      if( shift[i] == cell_idx_t(-1) )
+         interval.max()[i] += cell_idx_t(4);
+       //interval.max()[i] += cell_idx_t(2);
+      if( shift[i] == cell_idx_t( 1) )
+         interval.min()[i] -= cell_idx_t(4);
+       //interval.min()[i] -= cell_idx_t(2);
+   }
+
+#ifndef NDEBUG
+   CellInterval expandedCellBB( cellBB );
+   expandedCellBB.expand( cell_idx_t(4) );
+   WALBERLA_ASSERT( expandedCellBB.contains( interval ) );
+#endif
+
+   return interval;
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline CellInterval PdfFieldPackInfo< LatticeModel_T >::fineToCoarsePackInterval( stencil::Direction dir, const CellInterval & cellBB )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline CellInterval PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::fineToCoarsePackInterval( stencil::Direction dir, const CellInterval & cellBB )
+#endif
+{
+   return equalLevelUnpackInterval( dir, cellBB, uint_t(2) );
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+CellInterval PdfFieldPackInfo< LatticeModel_T >::fineToCoarseUnpackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                             const BlockID & smallBlock )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+CellInterval PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::fineToCoarseUnpackInterval( stencil::Direction dir, const CellInterval & cellBB,
+                                                                                                 const BlockID & smallBlock )
+#endif
+{
+   CellInterval interval = equalLevelPackInterval( dir, cellBB, uint_t(1) );
+   Vector3< cell_idx_t > shift = getNeighborShift( smallBlock, dir );
+
+   WALBERLA_ASSERT( divisibleByTwo( cellBB ) );
+
+   for( uint_t i = 0; i != Stencil::D; ++i )
+   {
+      if( shift[i] == cell_idx_t(-1) )
+         interval.max()[i] = interval.min()[i] + cell_idx_c( cellBB.size(i) / uint_t(2) ) - cell_idx_t(1);
+      if( shift[i] == cell_idx_t( 1) )
+         interval.min()[i] = interval.max()[i] - cell_idx_c( cellBB.size(i) / uint_t(2) ) + cell_idx_t(1);
+   }
+
+   WALBERLA_ASSERT( cellBB.contains( interval ) );
+
+   return interval;
+}
+
+
+
+//////////////////////////////
+// General helper functions //
+//////////////////////////////
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline uint_t PdfFieldPackInfo< LatticeModel_T >::equalLevelCells()
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline uint_t PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::equalLevelCells()
+#endif
+{
+   if( Stencil::containsDir( stencil::TNE ) || Stencil::containsDir( stencil::TNW ) || Stencil::containsDir( stencil::TSE ) ||
+       Stencil::containsDir( stencil::TSW ) || Stencil::containsDir( stencil::BNE ) || Stencil::containsDir( stencil::BNW ) ||
+       Stencil::containsDir( stencil::BSE ) || Stencil::containsDir( stencil::BSW ) )
+      return uint_t(3);
+      
+   return uint_t(2);
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline bool PdfFieldPackInfo< LatticeModel_T >::isFaceDirection( stencil::Direction dir )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline bool PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::isFaceDirection( stencil::Direction dir )
+#endif
+{
+   return ( dir == stencil::N ) || ( dir == stencil::S ) || ( dir == stencil::W ) ||
+          ( dir == stencil::E ) || ( dir == stencil::T ) || ( dir == stencil::B );
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline bool PdfFieldPackInfo< LatticeModel_T >::isEdgeDirection( stencil::Direction dir )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline bool PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::isEdgeDirection( stencil::Direction dir )
+#endif
+{
+   return ( dir == stencil::NW ) || ( dir == stencil::NE ) || ( dir == stencil::SW ) || ( dir == stencil::SE ) ||
+          ( dir == stencil::TN ) || ( dir == stencil::TS ) || ( dir == stencil::TW ) || ( dir == stencil::TE ) ||
+          ( dir == stencil::BN ) || ( dir == stencil::BS ) || ( dir == stencil::BW ) || ( dir == stencil::BE );
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline bool PdfFieldPackInfo< LatticeModel_T >::isCornerDirection( stencil::Direction dir )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline bool PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::isCornerDirection( stencil::Direction dir )
+#endif
+{
+   return ( dir == stencil::TNE ) || ( dir == stencil::TNW ) || ( dir == stencil::TSE ) || ( dir == stencil::TSW ) ||
+          ( dir == stencil::BNE ) || ( dir == stencil::BNW ) || ( dir == stencil::BSE ) || ( dir == stencil::BSW );
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline bool PdfFieldPackInfo< LatticeModel_T >::blocksConnectedByFaces( const Block * block, const BlockID & neighbor )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline bool PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::blocksConnectedByFaces( const Block * block, const BlockID & neighbor )
+#endif
+{
+   const uint_t face[] = { uint_t(4), uint_t(10), uint_t(12), uint_t(13), uint_t(15), uint_t(21) };
+   for( int i = 0; i != 6; ++i )
+   {
+      for( uint_t n = 0; n != block->getNeighborhoodSectionSize( face[i] ); ++n )
+         if( block->getNeighborId( face[i], n ) == neighbor )
+            return true;
+   }
+   return false;
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline bool PdfFieldPackInfo< LatticeModel_T >::blocksConnectedByEdges( const Block * block, const BlockID & neighbor )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline bool PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::blocksConnectedByEdges( const Block * block, const BlockID & neighbor )
+#endif
+{
+   const uint_t face[] = { uint_t( 1), uint_t( 3), uint_t( 5), uint_t( 7), uint_t( 9), uint_t(11),
+                           uint_t(14), uint_t(16), uint_t(18), uint_t(20), uint_t(22), uint_t(24) };
+
+   for( int i = 0; i != 12; ++i )
+   {
+      for( uint_t n = 0; n != block->getNeighborhoodSectionSize( face[i] ); ++n )
+         if( block->getNeighborId( face[i], n ) == neighbor )
+            return true;
+   }
+   return false;
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+inline bool PdfFieldPackInfo< LatticeModel_T >::divisibleByTwo( const CellInterval & cellBB )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+inline bool PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::divisibleByTwo( const CellInterval & cellBB )
+#endif
+{
+   return ( ( cellBB.xSize() & uint_t(1) ) == uint_t(0) ) &&
+          ( ( cellBB.ySize() & uint_t(1) ) == uint_t(0) ) &&
+          ( ( Stencil::D == uint_t(2) && cellBB.zSize() == uint_t(1) ) || ( Stencil::D == uint_t(3) && ( cellBB.zSize() & uint_t(1) ) == uint_t(0) ) );
+}
+
+
+
+#ifdef NDEBUG
+template< typename LatticeModel_T >
+bool PdfFieldPackInfo< LatticeModel_T >::coarserNeighborExistsInVicinity( const Block * block, stencil::Direction dir )
+#else
+template< typename LatticeModel_T, typename BoundaryHandling_T >
+bool PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T >::coarserNeighborExistsInVicinity( const Block * block, stencil::Direction dir )
+#endif
+{
+   if( block->getLevel() == uint_t(0) )
+      return false;
+
+   Vector3<int> min( -1 );
+   Vector3<int> max(  1 );
+
+   for( uint_t i = 0; i != 3; ++i )
+   {
+      if( stencil::c[i][dir] == -1 ) max[i] = 0;
+      if( stencil::c[i][dir] ==  1 ) min[i] = 0;
+   }
+   if( LatticeModel_T::Stencil::D == uint_t(2) )
+      min[2] = max[2] = 0;
+
+   for( int z = min[2]; z <= max[2]; ++z ) {
+      for( int y = min[1]; y <= max[1]; ++y ) {
+         for( int x = min[0]; x <= max[0]; ++x )
+         {
+            if( x == 0 && y == 0 && z == 0 )
+               continue;
+            if( block->neighborhoodSectionHasLargerBlock( blockforest::getBlockNeighborhoodSectionIndex(x,y,z) ) )
+               return true;
+         }
+      }
+   }
+
+   return false;
+}
+
+
+
+} // namespace refinement
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/PdfFieldSyncPackInfo.h b/src/lbm/refinement_rebase/PdfFieldSyncPackInfo.h
new file mode 100644
index 000000000..0b68676b7
--- /dev/null
+++ b/src/lbm/refinement_rebase/PdfFieldSyncPackInfo.h
@@ -0,0 +1,44 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file PdfFieldSyncPackInfo.h
+//! \ingroup lbm
+//! \author Florian Schornbaum <florian.schornbaum@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "lbm/field/PdfField.h"
+#include "field/refinement/PackInfo.h"
+
+
+namespace walberla {
+namespace lbm {
+namespace refinement {
+
+template <typename LatticeModel_T>
+class PdfFieldSyncPackInfo : public field::refinement::PackInfo< PdfField<LatticeModel_T>, typename LatticeModel_T::Stencil >
+{
+public:
+   PdfFieldSyncPackInfo( const BlockDataID & fieldId )
+   : field::refinement::PackInfo< PdfField<LatticeModel_T>, typename LatticeModel_T::Stencil >( fieldId )
+   {
+   }
+};
+
+} // namespace refinement
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/RefinementFunctorWrapper.h b/src/lbm/refinement_rebase/RefinementFunctorWrapper.h
new file mode 100644
index 000000000..9ea0b99e1
--- /dev/null
+++ b/src/lbm/refinement_rebase/RefinementFunctorWrapper.h
@@ -0,0 +1,67 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file RefinementFunctorWrapper.h
+//! \ingroup lbm
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "domain_decomposition/BlockStorage.h"
+
+#include <functional>
+
+namespace walberla {
+namespace lbm {
+namespace refinement {
+
+class FunctorWrapper {
+public:
+   FunctorWrapper( const std::function<void()> & fct)
+         : fct_(fct) {
+   }
+
+   void operator()(const uint_t /*level*/, const uint_t /*executionCounter*/) {
+      fct_();
+   }
+
+private:
+   std::function<void(void)> fct_;
+};
+
+class SweepAsFunctorWrapper {
+public:
+   SweepAsFunctorWrapper( const std::function<void(IBlock * )> & fct,
+                          const shared_ptr <StructuredBlockStorage> & blockStorage )
+         : fct_(fct), blockStorage_(blockStorage) {
+   }
+
+   void operator()(const uint_t level, const uint_t /*executionCounter*/) {
+      for (auto blockIt = blockStorage_->begin(); blockIt != blockStorage_->end(); ++blockIt) {
+         if (blockStorage_->getLevel(*blockIt) != level) continue;
+         fct_(blockIt.get());
+      }
+   }
+
+private:
+   std::function<void(IBlock * )> fct_;
+   shared_ptr <StructuredBlockStorage> blockStorage_;
+};
+
+} // namespace refinement
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/TimeStep.h b/src/lbm/refinement_rebase/TimeStep.h
new file mode 100644
index 000000000..1be6a470d
--- /dev/null
+++ b/src/lbm/refinement_rebase/TimeStep.h
@@ -0,0 +1,1751 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file TimeStep.h
+//! \ingroup lbm
+//! \author Florian Schornbaum <florian.schornbaum@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "EqualLevelBorderStreamCorrection.h"
+#include "LinearExplosion.h"
+#include "PdfFieldPackInfo.h"
+#include "TimeStepPdfPackInfo.h"
+
+#include "blockforest/StructuredBlockForest.h"
+#include "blockforest/communication/NonUniformBufferedScheme.h"
+
+#include "core/debug/CheckFunctions.h"
+#include "core/debug/Debug.h"
+#include "core/math/Uint.h"
+#include "core/selectable/IsSetSelected.h"
+#include "core/timing/TimingPool.h"
+
+#include "lbm/lattice_model/NeighborsStencil.h"
+
+#include "stencil/D2Q9.h"
+#include "stencil/D3Q27.h"
+
+
+
+namespace walberla {
+namespace lbm {
+namespace refinement {
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+class TimeStep
+{
+public:
+   
+   typedef typename NeighborsStencil<LatticeModel_T>::type CommunicationStencil_T;
+
+   typedef std::function< void ( const uint_t, const uint_t ) >            VoidFunction;  // parameters: level, execution count
+   typedef std::function< void ( IBlock *, const uint_t, const uint_t ) >  BlockFunction; // parameters: block, level, execution count
+   
+private:
+
+   class VoidFunctionWrappper
+   {
+   public:
+      VoidFunctionWrappper( std::vector< std::pair< VoidFunction, std::string > > & functions, const uint_t index ) :
+         functions_( functions ), index_( index ) {}
+      void operator()( const uint_t level, const uint_t executionCount )
+      {
+         functions_[index_].first( level, executionCount );
+      }
+   private:
+      std::vector< std::pair< VoidFunction, std::string > > & functions_;
+      uint_t index_;
+   };
+   
+   class BlockFunctionWrappper
+   {
+   public:
+      BlockFunctionWrappper( std::vector< std::pair< BlockFunction, std::string > > & functions, const uint_t index ) :
+         functions_( functions ), index_( index ) {}
+      void operator()( IBlock * block, const uint_t level, const uint_t executionCount )
+      {
+         functions_[index_].first( block, level, executionCount );
+      }
+   private:
+      std::vector< std::pair< BlockFunction, std::string > > & functions_;
+      uint_t index_;
+   };
+
+   template< typename F >
+   class SharedVoidFunctor
+   {
+   public:
+      SharedVoidFunctor( const shared_ptr<F> & functorPtr ) : functorPtr_( functorPtr ) { }
+      void operator()( const uint_t level, const uint_t executionCount ){ (*functorPtr_)( level, executionCount ); }
+   private:
+      shared_ptr<F> functorPtr_;
+   };
+
+   template< typename F >
+   class SharedBlockFunctor
+   {
+   public:
+      SharedBlockFunctor( const shared_ptr<F> & functorPtr ) : functorPtr_( functorPtr ) { }
+      void operator()( IBlock * block, const uint_t level, const uint_t executionCount ){ (*functorPtr_)( block, level, executionCount ); }
+   private:
+      shared_ptr<F> functorPtr_;
+   };
+
+public:
+
+   typedef typename LatticeModel_T::Stencil Stencil;
+
+   static const uint_t StreamIncludedGhostLayers = 2;
+
+   // constructors
+
+   TimeStep( weak_ptr< StructuredBlockForest > blocks, shared_ptr< Sweep_T > & sweep,
+             const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId,
+             const Set<SUID> & requiredBlockSelectors = Set<SUID>::emptySet(),
+             const Set<SUID> & incompatibleBlockSelectors = Set<SUID>::emptySet() );
+
+   TimeStep( weak_ptr< StructuredBlockForest > blocks, shared_ptr< Sweep_T > & sweep,
+             const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId,
+             const shared_ptr< TimeStepPdfPackInfo > & pdfPackInfo,
+             const Set<SUID> & requiredBlockSelectors = Set<SUID>::emptySet(),
+             const Set<SUID> & incompatibleBlockSelectors = Set<SUID>::emptySet() );
+   
+   // member functions
+ 
+   bool asynchronousCommunicationIsUsed() const { return asynchronousCommunication_; }
+   void asynchronousCommunication( const bool value = true ) { asynchronousCommunication_ = value; }
+
+   bool optimizedCommunicationIsUsed() const { return optimizedCommunication_; }
+   void optimizeCommunication( const bool value = true )
+   {
+      optimizedCommunication_ = value;
+      pdfPackInfo_->optimizeEqualLevelCommunication( optimizedCommunication_ );
+      pdfPackInfo_->optimizeForLinearExplosion( optimizedCommunication_ && performLinearExplosion_ );
+   }
+
+   bool equalLevelBorderStreamCorrectionIsPerformed() const { return performEqualLevelBorderStreamCorrection_; }
+   void performEqualLevelBorderStreamCorrection( const bool value = true )
+   {
+      performEqualLevelBorderStreamCorrection_ = value;
+   }
+   
+   bool linearExplosionIsPerformed() const { return performLinearExplosion_; }
+   void performLinearExplosion( const bool value = true )
+   {
+      performLinearExplosion_ = value;
+      pdfPackInfo_->optimizeForLinearExplosion( optimizedCommunication_ && performLinearExplosion_ );
+   }
+
+   void deactivateTiming() { timing_ = false; }
+   void enableTiming( const shared_ptr<WcTimingPool> & timingPool, const shared_ptr<WcTimingPool> & levelwiseTimingPool )
+   {
+      timing_ = true;
+      timingPool_ = timingPool;
+      levelwiseTimingPool_ = levelwiseTimingPool;
+      refresh( postCollideVoidFunctions_.size() );
+   }
+
+   void enableTiming()
+   {
+      timing_ = true;
+      if( !timingPool_ )
+         timingPool_ = make_shared<WcTimingPool>();
+      if( !levelwiseTimingPool_ )
+         levelwiseTimingPool_ = make_shared<WcTimingPool>();
+      refresh( postCollideVoidFunctions_.size() );
+   }
+
+   const shared_ptr<WcTimingPool> & getTimingPool()          const { return timingPool_;          }
+   const shared_ptr<WcTimingPool> & getLevelWiseTimingPool() const { return levelwiseTimingPool_; }
+
+   void operator()()
+   {
+      auto blocks = blocks_.lock();
+      WALBERLA_CHECK_NOT_NULLPTR( blocks, "Trying to access 'TimeStep' (refinement) for a block storage object that doesn't exist anymore" );
+      if( blocks->getNumberOfLevels() > postCollideVoidFunctions_.size() )
+         refresh( blocks->getNumberOfLevels() );
+      recursiveStep( uint_t(0), uint_t(0) );
+   }
+
+   void addPackInfo( const typename blockforest::communication::NonUniformBufferedScheme< CommunicationStencil_T >::PackInfo & packInfo )
+   {
+      communication_.addPackInfo( packInfo );
+   }
+   
+   inline void addPostCollideVoidFunction ( const  VoidFunction & function, const std::string & identifier );
+   inline void addPostCollideBlockFunction( const BlockFunction & function, const std::string & identifier );
+   inline void addPostCollideVoidFunction ( const  VoidFunction & function, const std::string & identifier, const uint_t level );
+   inline void addPostCollideBlockFunction( const BlockFunction & function, const std::string & identifier, const uint_t level );
+   
+   inline void addPostBoundaryHandlingVoidFunction ( const  VoidFunction & function, const std::string & identifier );
+   inline void addPostBoundaryHandlingBlockFunction( const BlockFunction & function, const std::string & identifier );
+   inline void addPostBoundaryHandlingVoidFunction ( const  VoidFunction & function, const std::string & identifier, const uint_t level );
+   inline void addPostBoundaryHandlingBlockFunction( const BlockFunction & function, const std::string & identifier, const uint_t level );
+   
+   inline void addPostStreamVoidFunction ( const  VoidFunction & function, const std::string & identifier );
+   inline void addPostStreamBlockFunction( const BlockFunction & function, const std::string & identifier );
+   inline void addPostStreamVoidFunction ( const  VoidFunction & function, const std::string & identifier, const uint_t level );
+   inline void addPostStreamBlockFunction( const BlockFunction & function, const std::string & identifier, const uint_t level );
+   
+   template< typename F >
+   inline void addPostCollideVoidFunction ( const shared_ptr<F> & functorPtr, const std::string & identifier );
+   template< typename F >
+   inline void addPostCollideBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier );
+   template< typename F >
+   inline void addPostCollideVoidFunction ( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level );
+   template< typename F >
+   inline void addPostCollideBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level );
+
+   template< typename F >
+   inline void addPostBoundaryHandlingVoidFunction ( const shared_ptr<F> & functorPtr, const std::string & identifier );
+   template< typename F >
+   inline void addPostBoundaryHandlingBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier );
+   template< typename F >
+   inline void addPostBoundaryHandlingVoidFunction ( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level );
+   template< typename F >
+   inline void addPostBoundaryHandlingBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level );
+
+   template< typename F >
+   inline void addPostStreamVoidFunction ( const shared_ptr<F> & functorPtr, const std::string & identifier );
+   template< typename F >
+   inline void addPostStreamBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier );
+   template< typename F >
+   inline void addPostStreamVoidFunction ( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level );
+   template< typename F >
+   inline void addPostStreamBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level );
+
+private:
+
+   void init( const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId );
+   void consistencyChecks( const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId ) const;
+   void refresh( const uint_t levels );
+
+   std::string getTimingPoolString( const std::string & name, const std::string & suffix = std::string("") ) const;
+   std::string getLevelwiseTimingPoolString( const std::string & name, const uint_t level, const std::string & suffix = std::string("") ) const;
+
+   void createTimers( const uint_t levels );
+   
+   template< typename Function >
+   void addFunction( std::vector< std::vector< std::pair< Function, std::string > > > & functions,
+                     const Function & function, const std::string & identifier );
+   
+   template< typename Function >
+   void addFunction( std::vector< std::vector< std::pair< Function, std::string > > > & functions,
+                     const Function & function, const std::string & identifier, const uint_t level );
+
+   std::vector< Block * > selectedBlocks( const uint_t level ) const;
+
+   inline void startTiming( const std::string & name, const uint_t level, const std::string & suffix = std::string("") );
+   inline void  stopTiming( const std::string & name, const uint_t level, const std::string & suffix = std::string("") );
+
+   void collide( std::vector< Block * > & blocks, const uint_t level, const uint_t executionCount );
+   void stream( std::vector< Block * > & blocks, const uint_t level, const uint_t executionCount );
+   void finishStream( std::vector< Block * > & blocks, const uint_t level, const uint_t executionCount );
+   void streamCollide( std::vector< Block * > & blocks, const uint_t level, const uint_t executionCount );
+
+   void startCommunicationEqualLevel( const uint_t level );
+   void   endCommunicationEqualLevel( const uint_t level );
+   void startCommunicationCoarseToFine( const uint_t level );
+   void   endCommunicationCoarseToFine( const uint_t level );
+   void startCommunicationFineToCoarse( const uint_t level );
+   void   endCommunicationFineToCoarse( const uint_t level );
+
+   void performLinearExplosion( std::vector< Block * > & blocks, const uint_t level );
+
+   void recursiveStep( const uint_t level, const uint_t executionCount );
+   
+   
+   
+   weak_ptr< StructuredBlockForest > blocks_;
+
+            shared_ptr< Sweep_T >          sweep_;                   // stream & collide
+   typename BoundaryHandling_T::BlockSweep boundarySweep_;           // pre-stream boundary treatment
+   typename BoundaryHandling_T::BlockSweep boundarySweepWithLayers_; // pre-stream boundary treatment (including ghost layers)
+
+   bool asynchronousCommunication_;
+   bool optimizedCommunication_;
+
+   shared_ptr< TimeStepPdfPackInfo > pdfPackInfo_;
+
+   blockforest::communication::NonUniformBufferedScheme< CommunicationStencil_T > communication_;
+
+   bool performEqualLevelBorderStreamCorrection_;
+   EqualLevelBorderStreamCorrection< LatticeModel_T > equalLevelBorderStreamCorrection_;
+
+   bool performLinearExplosion_;
+   LinearExplosion< LatticeModel_T, BoundaryHandling_T > linearExplosion_;
+
+   bool timing_;
+   shared_ptr<WcTimingPool> timingPool_;
+   shared_ptr<WcTimingPool> levelwiseTimingPool_;
+   
+   Set<SUID> requiredBlockSelectors_;
+   Set<SUID> incompatibleBlockSelectors_;
+   
+   std::vector< std::pair< VoidFunction,  std::string > >  globalPostCollideVoidFunctions_;
+   std::vector< std::pair< BlockFunction, std::string > >  globalPostCollideBlockFunctions_;
+   
+   std::vector< std::pair< VoidFunction,  std::string > >  globalPostBoundaryHandlingVoidFunctions_;
+   std::vector< std::pair< BlockFunction, std::string > >  globalPostBoundaryHandlingBlockFunctions_;
+   
+   std::vector< std::pair< VoidFunction,  std::string > >  globalPostStreamVoidFunctions_;
+   std::vector< std::pair< BlockFunction, std::string > >  globalPostStreamBlockFunctions_;
+
+   std::vector< std::vector< std::pair< VoidFunction,  std::string > > >  postCollideVoidFunctions_;
+   std::vector< std::vector< std::pair< BlockFunction, std::string > > >  postCollideBlockFunctions_;
+
+   std::vector< std::vector< std::pair< VoidFunction,  std::string > > >  postBoundaryHandlingVoidFunctions_;
+   std::vector< std::vector< std::pair< BlockFunction, std::string > > >  postBoundaryHandlingBlockFunctions_;
+
+   std::vector< std::vector< std::pair< VoidFunction,  std::string > > >  postStreamVoidFunctions_;
+   std::vector< std::vector< std::pair< BlockFunction, std::string > > >  postStreamBlockFunctions_;
+
+}; // class TimeStep
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+const uint_t TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::StreamIncludedGhostLayers;
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::TimeStep( weak_ptr< StructuredBlockForest > blocks, shared_ptr< Sweep_T > & sweep,
+                                                                   const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId,
+                                                                   const Set<SUID> & requiredBlockSelectors /*= Set<SUID>::emptySet()*/,
+                                                                   const Set<SUID> & incompatibleBlockSelectors /*= Set<SUID>::emptySet()*/ ) :
+   blocks_( blocks ), sweep_( sweep ),
+   boundarySweep_( BoundaryHandling_T::getBlockSweep( boundaryHandlingId, uint_t(0) ) ),
+   boundarySweepWithLayers_( BoundaryHandling_T::getBlockSweep( boundaryHandlingId, StreamIncludedGhostLayers ) ),
+   asynchronousCommunication_( true ), optimizedCommunication_( true ),
+#ifdef NDEBUG   
+   pdfPackInfo_( make_shared< lbm::refinement::PdfFieldPackInfo< LatticeModel_T > >( pdfFieldId, true, true ) ),
+#else
+   pdfPackInfo_( make_shared< lbm::refinement::PdfFieldPackInfo< LatticeModel_T, BoundaryHandling_T > >( pdfFieldId, boundaryHandlingId, true, true ) ),
+#endif
+   communication_( blocks, requiredBlockSelectors, incompatibleBlockSelectors ),
+   performEqualLevelBorderStreamCorrection_( true ), equalLevelBorderStreamCorrection_( pdfFieldId ),
+   performLinearExplosion_( true ), linearExplosion_( pdfFieldId, boundaryHandlingId ),
+   timing_( false ),
+   requiredBlockSelectors_( requiredBlockSelectors ), incompatibleBlockSelectors_( incompatibleBlockSelectors )
+{
+   init( pdfFieldId, boundaryHandlingId );
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::TimeStep( weak_ptr< StructuredBlockForest > blocks, shared_ptr< Sweep_T > & sweep,
+                                                                   const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId,
+                                                                   const shared_ptr< TimeStepPdfPackInfo > & pdfPackInfo,
+                                                                   const Set<SUID> & requiredBlockSelectors /*= Set<SUID>::emptySet()*/,
+                                                                   const Set<SUID> & incompatibleBlockSelectors /*= Set<SUID>::emptySet()*/ ) :
+   blocks_( blocks ), sweep_( sweep ),
+   boundarySweep_( BoundaryHandling_T::getBlockSweep( boundaryHandlingId, uint_t(0) ) ),
+   boundarySweepWithLayers_( BoundaryHandling_T::getBlockSweep( boundaryHandlingId, StreamIncludedGhostLayers ) ),
+   asynchronousCommunication_( true ), optimizedCommunication_( true ),
+   pdfPackInfo_( pdfPackInfo ),
+   communication_( blocks, requiredBlockSelectors, incompatibleBlockSelectors ),
+   performEqualLevelBorderStreamCorrection_( true ), equalLevelBorderStreamCorrection_( pdfFieldId ),
+   performLinearExplosion_( true ), linearExplosion_( pdfFieldId, boundaryHandlingId ),
+   timing_( false ),
+   requiredBlockSelectors_( requiredBlockSelectors ), incompatibleBlockSelectors_( incompatibleBlockSelectors )
+{
+   init( pdfFieldId, boundaryHandlingId );
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::init( const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId )
+{   
+   communication_.addPackInfo( pdfPackInfo_ );
+   communication_.setLocalMode( blockforest::WAIT );
+   consistencyChecks( pdfFieldId, boundaryHandlingId );
+   auto blocks = blocks_.lock();
+   WALBERLA_CHECK_NOT_NULLPTR( blocks, "Trying to access 'TimeStep' (refinement) for a block storage object that doesn't exist anymore" );
+   refresh( blocks->getNumberOfLevels() );
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::consistencyChecks( const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId ) const
+{
+   // consistency checks / field validity checks
+
+   auto blocks = blocks_.lock();
+   WALBERLA_CHECK_NOT_NULLPTR( blocks, "Trying to access 'TimeStep' (refinement) for a block storage object that doesn't exist anymore" );
+   for( auto block = blocks->begin(); block != blocks->end(); ++block )
+   {
+      if( !selectable::isSetSelected( block->getState(), requiredBlockSelectors_, incompatibleBlockSelectors_ ) )
+         continue;
+
+      auto *  pdfField = block->template getData< PdfField< LatticeModel_T > >( pdfFieldId );
+      if( pdfField == NULL )
+      {
+         WALBERLA_ABORT( "Could not get the PDF field from block " << block->getId() << ". Check if it is allocated on "
+                         "the block and if the LatticeModel matches!" );
+      }
+
+      auto * boundaryHandling = block->template getData< BoundaryHandling_T >( boundaryHandlingId );
+      if( boundaryHandling == NULL )
+      {
+         WALBERLA_ABORT( "Could not get the boundary handling from block " << block->getId() << ". Check if it is "
+                         "allocated on the block and if its type matches!" );
+      }
+
+      auto * flagField = boundaryHandling->getFlagField();
+      WALBERLA_ASSERT_NOT_NULLPTR( flagField );
+
+      if( LatticeModel_T::Stencil::D == uint_t(3) )
+      {
+         if( ( pdfField->xSize() & uint_t(1) ) == uint_t(1) || ( pdfField->ySize() & uint_t(1) ) == uint_t(1) ||
+             ( pdfField->zSize() & uint_t(1) ) == uint_t(1) )
+            WALBERLA_ABORT( "The x-, y-, and z-size of the PDF field on each block must be divisible by two!\n"
+                            "(PDF field size on block " << block->getId() <<
+                            " is " << pdfField->xSize() << " x " << pdfField->ySize() << " x " << pdfField->zSize() << ")" );
+
+         if( pdfField->xSize() < uint_t(4) || pdfField->ySize() < uint_t(4) || pdfField->zSize() < uint_t(4) )
+            WALBERLA_ABORT( "The size of the PDF field on each block must be at least 4x4x4 cells!\n"
+                            "(PDF field size on block " << block->getId() <<
+                            " is " << pdfField->xSize() << " x " << pdfField->ySize() << " x " << pdfField->zSize() << ")" );
+      }
+      else
+      {
+         WALBERLA_CHECK_EQUAL( LatticeModel_T::Stencil::D, uint_t(2) );
+
+         if( ( pdfField->xSize() & uint_t(1) ) == uint_t(1) || ( pdfField->ySize() & uint_t(1) ) == uint_t(1) )
+            WALBERLA_ABORT( "The x- and y-size of the PDF field on each block must be divisible by two!\n"
+                            "(PDF field size on block " << block->getId() <<
+                            " is " << pdfField->xSize() << " x " << pdfField->ySize() << " x " << pdfField->zSize() << ")" );
+
+         if( pdfField->xSize() < uint_t(4) || pdfField->ySize() < uint_t(4) )
+            WALBERLA_ABORT( "The size of the PDF field on each block must be at least 4x4x1 cells!\n"
+                            "(PDF field size on block " << block->getId() <<
+                            " is " << pdfField->xSize() << " x " << pdfField->ySize() << " x " << pdfField->zSize() << ")" );
+
+         if( pdfField->zSize() != uint_t(1) )
+            WALBERLA_ABORT( "The z-size of the PDF field on each block must be 1!\n"
+                            "(PDF field size on block " << block->getId() <<
+                            " is " << pdfField->xSize() << " x " << pdfField->ySize() << " x " << pdfField->zSize() << ")" );
+      }
+
+      if( pdfField->xSize() != flagField->xSize() || pdfField->ySize() != flagField->ySize() || pdfField->zSize() != flagField->zSize() )
+         WALBERLA_ABORT( "The size of the PDF field must be identical to the size of the flag field!\n"
+                         "(PDF field [" << pdfField->xSize() << " x " << pdfField->ySize() << " x " << pdfField->zSize() <<
+                         "] vs. flag field [" << flagField->xSize() << " x " << flagField->ySize() << " x " << flagField->zSize() << "])" );
+
+      if( pdfField->nrOfGhostLayers() < uint_t(4) )
+         WALBERLA_ABORT( "The PDF field of each block needs at least 4 ghost layers!\n"
+                         "(currently only possesses " << pdfField->nrOfGhostLayers() << " on block " << block->getId() << ")" );
+
+      if( flagField->nrOfGhostLayers() < uint_t(4) )
+         WALBERLA_ABORT( "The flag field of each block needs at least 4 ghost layers!\n"
+                         "(currently only possesses " << flagField->nrOfGhostLayers() << " on block " << block->getId() << ")" );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::refresh( const uint_t levels )
+{
+   createTimers( levels );
+
+   if( levels > postCollideVoidFunctions_.size() )
+   {
+      const uint_t previousLevels = postCollideVoidFunctions_.size();
+      
+      postCollideVoidFunctions_.resize( levels );
+      postCollideBlockFunctions_.resize( levels );
+
+      postBoundaryHandlingVoidFunctions_.resize( levels );
+      postBoundaryHandlingBlockFunctions_.resize( levels );
+
+      postStreamVoidFunctions_.resize( levels );
+      postStreamBlockFunctions_.resize( levels );
+      
+      for( uint_t f = uint_t(0); f != globalPostCollideVoidFunctions_.size(); ++f )
+      {
+         VoidFunctionWrappper wrappedFunction( globalPostCollideVoidFunctions_, f );
+         const std::string identifier = globalPostCollideVoidFunctions_[f].second;
+         for( uint_t i = previousLevels; i != levels; ++i )
+         {
+            postCollideVoidFunctions_[i].push_back( std::make_pair( wrappedFunction, identifier ) );
+            if( timing_ && ! levelwiseTimingPool_->timerExists( getLevelwiseTimingPoolString( identifier, i ) ) )
+               levelwiseTimingPool_->registerTimer( getLevelwiseTimingPoolString( identifier, i ) );
+         }
+      }
+
+      for( uint_t f = uint_t(0); f != globalPostCollideBlockFunctions_.size(); ++f )
+      {
+         BlockFunctionWrappper wrappedFunction( globalPostCollideBlockFunctions_, f );
+         const std::string identifier = globalPostCollideBlockFunctions_[f].second;
+         for( uint_t i = previousLevels; i != levels; ++i )
+         {
+            postCollideBlockFunctions_[i].push_back( std::make_pair( wrappedFunction, identifier ) );
+            if( timing_ && ! levelwiseTimingPool_->timerExists( getLevelwiseTimingPoolString( identifier, i ) ) )
+               levelwiseTimingPool_->registerTimer( getLevelwiseTimingPoolString( identifier, i ) );
+         }
+      }
+      
+      for( uint_t f = uint_t(0); f != globalPostBoundaryHandlingVoidFunctions_.size(); ++f )
+      {
+         VoidFunctionWrappper wrappedFunction( globalPostBoundaryHandlingVoidFunctions_, f );
+         const std::string identifier = globalPostBoundaryHandlingVoidFunctions_[f].second;
+         for( uint_t i = previousLevels; i != levels; ++i )
+         {
+            postBoundaryHandlingVoidFunctions_[i].push_back( std::make_pair( wrappedFunction, identifier ) );
+            if( timing_ && ! levelwiseTimingPool_->timerExists( getLevelwiseTimingPoolString( identifier, i ) ) )
+               levelwiseTimingPool_->registerTimer( getLevelwiseTimingPoolString( identifier, i ) );
+         }
+      }
+      
+      for( uint_t f = uint_t(0); f != globalPostBoundaryHandlingBlockFunctions_.size(); ++f )
+      {
+         BlockFunctionWrappper wrappedFunction( globalPostBoundaryHandlingBlockFunctions_, f );
+         const std::string identifier = globalPostBoundaryHandlingBlockFunctions_[f].second;
+         for( uint_t i = previousLevels; i != levels; ++i )
+         {
+            postBoundaryHandlingBlockFunctions_[i].push_back( std::make_pair( wrappedFunction, identifier ) );
+            if( timing_ && ! levelwiseTimingPool_->timerExists( getLevelwiseTimingPoolString( identifier, i ) ) )
+               levelwiseTimingPool_->registerTimer( getLevelwiseTimingPoolString( identifier, i ) );
+         }
+      }
+      
+      for( uint_t f = uint_t(0); f != globalPostStreamVoidFunctions_.size(); ++f )
+      {
+         VoidFunctionWrappper wrappedFunction( globalPostStreamVoidFunctions_, f );
+         const std::string identifier = globalPostStreamVoidFunctions_[f].second;
+         for( uint_t i = previousLevels; i != levels; ++i )
+         {
+            postStreamVoidFunctions_[i].push_back( std::make_pair( wrappedFunction, identifier ) );
+            if( timing_ && ! levelwiseTimingPool_->timerExists( getLevelwiseTimingPoolString( identifier, i ) ) )
+               levelwiseTimingPool_->registerTimer( getLevelwiseTimingPoolString( identifier, i ) );
+         }
+      }
+      
+      for( uint_t f = uint_t(0); f != globalPostStreamBlockFunctions_.size(); ++f )
+      {
+         BlockFunctionWrappper wrappedFunction( globalPostStreamBlockFunctions_, f );
+         const std::string identifier = globalPostStreamBlockFunctions_[f].second;
+         for( uint_t i = previousLevels; i != levels; ++i )
+         {
+            postStreamBlockFunctions_[i].push_back( std::make_pair( wrappedFunction, identifier ) );
+            if( timing_ && ! levelwiseTimingPool_->timerExists( getLevelwiseTimingPoolString( identifier, i ) ) )
+               levelwiseTimingPool_->registerTimer( getLevelwiseTimingPoolString( identifier, i ) );
+         }
+      }
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename Function >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addFunction( std::vector< std::vector< std::pair< Function, std::string > > > & functions,
+                                                                           const Function & function, const std::string & identifier )
+{
+   for( uint_t i = uint_t(0); i < functions.size(); ++i )
+   {
+      functions[i].push_back( std::make_pair( function, identifier ) );
+      if( timing_ && ! levelwiseTimingPool_->timerExists( getLevelwiseTimingPoolString( identifier, i ) ) )
+         levelwiseTimingPool_->registerTimer( getLevelwiseTimingPoolString( identifier, i ) );
+   }
+   if( timing_ && ! timingPool_->timerExists( getTimingPoolString( identifier ) ) )
+      timingPool_->registerTimer( getTimingPoolString( identifier ) );
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename Function >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addFunction( std::vector< std::vector< std::pair< Function, std::string > > > & functions,
+                                                                           const Function & function, const std::string & identifier,
+                                                                           const uint_t level )
+{
+   if( level > ( postCollideVoidFunctions_.size() - uint_t(1) ) )
+      refresh( level + uint_t(1) );
+   functions[level].push_back( std::make_pair( function, identifier ) );
+   if( timing_ && ! levelwiseTimingPool_->timerExists( getLevelwiseTimingPoolString( identifier, level ) ) )
+      levelwiseTimingPool_->registerTimer( getLevelwiseTimingPoolString( identifier, level ) );
+   if( timing_ && ! timingPool_->timerExists( getTimingPoolString( identifier ) ) )
+      timingPool_->registerTimer( getTimingPoolString( identifier ) );
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostCollideVoidFunction( const VoidFunction & function, const std::string & identifier )
+{
+   VoidFunctionWrappper wrappedFunction( globalPostCollideVoidFunctions_, globalPostCollideVoidFunctions_.size() );
+   globalPostCollideVoidFunctions_.push_back( std::make_pair( function, identifier ) );
+   addFunction< VoidFunction >( postCollideVoidFunctions_, wrappedFunction, identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostCollideBlockFunction( const BlockFunction & function, const std::string & identifier )
+{
+   BlockFunctionWrappper wrappedFunction( globalPostCollideBlockFunctions_, globalPostCollideBlockFunctions_.size() );
+   globalPostCollideBlockFunctions_.push_back( std::make_pair( function, identifier ) );
+   addFunction< BlockFunction >( postCollideBlockFunctions_, wrappedFunction, identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostCollideVoidFunction( const VoidFunction & function, const std::string & identifier, const uint_t level )
+{
+   addFunction< VoidFunction >( postCollideVoidFunctions_, function, identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostCollideBlockFunction( const BlockFunction & function, const std::string & identifier, const uint_t level )
+{
+   addFunction< BlockFunction >( postCollideBlockFunctions_, function, identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostBoundaryHandlingVoidFunction( const VoidFunction & function, const std::string & identifier )
+{
+   VoidFunctionWrappper wrappedFunction( globalPostBoundaryHandlingVoidFunctions_, globalPostBoundaryHandlingVoidFunctions_.size() );
+   globalPostBoundaryHandlingVoidFunctions_.push_back( std::make_pair( function, identifier ) );
+   addFunction< VoidFunction >( postBoundaryHandlingVoidFunctions_, wrappedFunction, identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostBoundaryHandlingBlockFunction( const BlockFunction & function, const std::string & identifier )
+{
+   BlockFunctionWrappper wrappedFunction( globalPostBoundaryHandlingBlockFunctions_, globalPostBoundaryHandlingBlockFunctions_.size() );
+   globalPostBoundaryHandlingBlockFunctions_.push_back( std::make_pair( function, identifier ) );
+   addFunction< BlockFunction >( postBoundaryHandlingBlockFunctions_, wrappedFunction, identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostBoundaryHandlingVoidFunction( const VoidFunction & function, const std::string & identifier, const uint_t level )
+{
+   addFunction< VoidFunction >( postBoundaryHandlingVoidFunctions_, function, identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostBoundaryHandlingBlockFunction( const BlockFunction & function, const std::string & identifier, const uint_t level )
+{
+   addFunction< BlockFunction >( postBoundaryHandlingBlockFunctions_, function, identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostStreamVoidFunction( const VoidFunction & function, const std::string & identifier )
+{
+   VoidFunctionWrappper wrappedFunction( globalPostStreamVoidFunctions_, globalPostStreamVoidFunctions_.size() );
+   globalPostStreamVoidFunctions_.push_back( std::make_pair( function, identifier ) );
+   addFunction< VoidFunction >( postStreamVoidFunctions_, wrappedFunction, identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostStreamBlockFunction( const BlockFunction & function, const std::string & identifier )
+{
+   BlockFunctionWrappper wrappedFunction( globalPostStreamBlockFunctions_, globalPostStreamBlockFunctions_.size() );
+   globalPostStreamBlockFunctions_.push_back( std::make_pair( function, identifier ) );
+   addFunction< BlockFunction >( postStreamBlockFunctions_, wrappedFunction, identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostStreamVoidFunction( const VoidFunction & function, const std::string & identifier, const uint_t level )
+{
+   addFunction< VoidFunction >( postStreamVoidFunctions_, function, identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostStreamBlockFunction( const BlockFunction & function, const std::string & identifier, const uint_t level )
+{
+   addFunction< BlockFunction >( postStreamBlockFunctions_, function, identifier, level );
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostCollideVoidFunction( const shared_ptr<F> & functorPtr, const std::string & identifier )
+{
+   addPostCollideVoidFunction( SharedVoidFunctor<F>(functorPtr), identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostCollideBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier )
+{
+   addPostCollideBlockFunction( SharedBlockFunctor<F>(functorPtr), identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostCollideVoidFunction( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level )
+{
+   addPostCollideVoidFunction( SharedVoidFunctor<F>(functorPtr), identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostCollideBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level )
+{
+   addPostCollideBlockFunction( SharedBlockFunctor<F>(functorPtr), identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostBoundaryHandlingVoidFunction( const shared_ptr<F> & functorPtr, const std::string & identifier )
+{
+   addPostBoundaryHandlingVoidFunction( SharedVoidFunctor<F>(functorPtr), identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostBoundaryHandlingBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier )
+{
+   addPostBoundaryHandlingBlockFunction( SharedBlockFunctor<F>(functorPtr), identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostBoundaryHandlingVoidFunction( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level )
+{
+   addPostBoundaryHandlingVoidFunction( SharedVoidFunctor<F>(functorPtr), identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostBoundaryHandlingBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level )
+{
+   addPostBoundaryHandlingBlockFunction( SharedBlockFunctor<F>(functorPtr), identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostStreamVoidFunction( const shared_ptr<F> & functorPtr, const std::string & identifier )
+{
+   addPostStreamVoidFunction( SharedVoidFunctor<F>(functorPtr), identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostStreamBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier )
+{
+   addPostStreamBlockFunction( SharedBlockFunctor<F>(functorPtr), identifier );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostStreamVoidFunction( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level )
+{
+   addPostStreamVoidFunction( SharedVoidFunctor<F>(functorPtr), identifier, level );
+}
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+template< typename F >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::addPostStreamBlockFunction( const shared_ptr<F> & functorPtr, const std::string & identifier, const uint_t level )
+{
+   addPostStreamBlockFunction( SharedBlockFunctor<F>(functorPtr), identifier, level );
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+std::string TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::getTimingPoolString( const std::string & name,
+                                                                                          const std::string & suffix ) const
+{
+   std::ostringstream oss;
+   oss  << name;
+   if( !suffix.empty() )
+      oss  << " " << suffix;
+   return oss.str();
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+std::string TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::getLevelwiseTimingPoolString( const std::string & name, const uint_t level,
+                                                                                                   const std::string & suffix ) const
+{
+   std::ostringstream oss;
+   oss  << name << " (" << level << ")";
+   if( !suffix.empty() )
+      oss  << " " << suffix;
+   return oss.str();
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::createTimers( const uint_t levels )
+{
+   // unify registered timers across all processes
+   
+   if( timing_ )
+   {
+      std::vector< std::string > timers;
+      timers.push_back( getTimingPoolString( "boundary handling" ) );
+      timers.push_back( getTimingPoolString( "collide" ) );
+      timers.push_back( getTimingPoolString( "communication coarse to fine", "[pack & send]" ) );
+      timers.push_back( getTimingPoolString( "communication coarse to fine", "[wait & unpack]" ) );
+      timers.push_back( getTimingPoolString( "communication equal level", "[pack & send]" ) );
+      timers.push_back( getTimingPoolString( "communication equal level", "[wait & unpack]" ) );
+      timers.push_back( getTimingPoolString( "communication fine to coarse", "[pack & send]" ) );
+      timers.push_back( getTimingPoolString( "communication fine to coarse", "[wait & unpack]" ) );
+      timers.push_back( getTimingPoolString( "equal level border stream correction", "[prepare]" ) );
+      timers.push_back( getTimingPoolString( "equal level border stream correction", "[apply]" ) );
+      timers.push_back( getTimingPoolString( "linear explosion" ) );
+      timers.push_back( getTimingPoolString( "stream" ) );
+      timers.push_back( getTimingPoolString( "stream & collide" ) );
+      
+      for( auto timer = timers.begin(); timer != timers.end(); ++timer )
+         if( ! timingPool_->timerExists( *timer ) )
+            timingPool_->registerTimer( *timer );
+            
+      timers.clear();
+      timers.push_back( getLevelwiseTimingPoolString( "stream & collide", levels - uint_t(1) ) );
+      for( uint_t i = uint_t(0); i < levels; ++i )
+      {
+         timers.push_back( getLevelwiseTimingPoolString( "boundary handling", i ) );
+         timers.push_back( getLevelwiseTimingPoolString( "collide", i ) );
+         timers.push_back( getLevelwiseTimingPoolString( "communication equal level", i, "[pack & send]" ) );
+         timers.push_back( getLevelwiseTimingPoolString( "communication equal level", i, "[wait & unpack]" ) );
+         timers.push_back( getLevelwiseTimingPoolString( "linear explosion", i ) );
+         timers.push_back( getLevelwiseTimingPoolString( "stream", i ) );
+         if( i != uint_t(0) )
+         {
+            timers.push_back( getLevelwiseTimingPoolString( "communication coarse to fine", i, "[pack & send]" ) );
+            timers.push_back( getLevelwiseTimingPoolString( "communication coarse to fine", i, "[wait & unpack]" ) );
+            timers.push_back( getLevelwiseTimingPoolString( "communication fine to coarse", i, "[pack & send]" ) );
+            timers.push_back( getLevelwiseTimingPoolString( "communication fine to coarse", i, "[wait & unpack]" ) );
+         }
+         if( i != ( levels - uint_t(1) ) )
+         {
+            timers.push_back( getLevelwiseTimingPoolString( "equal level border stream correction", i, "[prepare]" ) );
+            timers.push_back( getLevelwiseTimingPoolString( "equal level border stream correction", i, "[apply]" ) );
+         }
+      }
+      
+      for( auto timer = timers.begin(); timer != timers.end(); ++timer )
+         if( ! levelwiseTimingPool_->timerExists( *timer ) )
+            levelwiseTimingPool_->registerTimer( *timer );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+std::vector< Block * > TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::selectedBlocks( const uint_t level ) const
+{
+   std::vector< Block * > levelBlocks;
+   auto blocks = blocks_.lock();
+   WALBERLA_CHECK_NOT_NULLPTR( blocks, "Trying to access 'TimeStep' (refinement) for a block storage object that doesn't exist anymore" );
+   blocks->getBlocks( levelBlocks, level );
+   
+   std::vector< Block * > sBlocks;
+   
+   for( auto block = levelBlocks.begin(); block != levelBlocks.end(); ++block )
+      if( selectable::isSetSelected( (*block)->getState(), requiredBlockSelectors_, incompatibleBlockSelectors_ ) )
+         sBlocks.push_back( *block );
+   
+   return sBlocks;
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::startTiming( const std::string & name, const uint_t level,
+                                                                                  const std::string & suffix )
+{
+   (*timingPool_)[ getTimingPoolString( name, suffix ) ].start();
+   (*levelwiseTimingPool_)[ getLevelwiseTimingPoolString( name, level, suffix ) ].start();
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+inline void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::stopTiming( const std::string & name, const uint_t level,
+                                                                                 const std::string & suffix )
+{
+   (*timingPool_)[ getTimingPoolString( name, suffix ) ].end();
+   (*levelwiseTimingPool_)[ getLevelwiseTimingPoolString( name, level, suffix ) ].end();
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::collide( std::vector< Block * > & blocks, const uint_t level, const uint_t executionCount )
+{
+   if( timing_ )
+   {
+      for( auto block = blocks.begin(); block != blocks.end(); ++block )
+      {
+         startTiming( "collide", level );
+         sweep_->collide( *block );
+         stopTiming( "collide", level );
+
+         for( auto func = postCollideBlockFunctions_[level].begin(); func != postCollideBlockFunctions_[level].end(); ++func )
+         {
+            startTiming( func->second, level );
+            (func->first)( *block, level, executionCount );
+            stopTiming( func->second, level );
+         }
+      }
+      for( auto func = postCollideVoidFunctions_[level].begin(); func != postCollideVoidFunctions_[level].end(); ++func )
+      {
+         startTiming( func->second, level );
+         (func->first)( level, executionCount );
+         stopTiming( func->second, level );
+      }
+   }
+   else
+   {
+      for( auto block = blocks.begin(); block != blocks.end(); ++block )
+      {
+         sweep_->collide( *block );
+
+         for( auto func = postCollideBlockFunctions_[level].begin(); func != postCollideBlockFunctions_[level].end(); ++func )
+            (func->first)( *block, level, executionCount );
+      }
+      for( auto func = postCollideVoidFunctions_[level].begin(); func != postCollideVoidFunctions_[level].end(); ++func )
+         (func->first)( level, executionCount );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::stream( std::vector< Block * > & blocks, const uint_t level, const uint_t executionCount )
+{
+   auto _blocks = blocks_.lock();
+   WALBERLA_CHECK_NOT_NULLPTR( _blocks, "Trying to access 'TimeStep' (refinement) for a block storage object that doesn't exist anymore" );
+
+   const uint_t finestLevel = _blocks->getNumberOfLevels() - uint_t(1);
+   const bool doEqualLevelBorderStreamCorrection = ( performEqualLevelBorderStreamCorrection_ && level != finestLevel );
+
+   if( timing_ )
+   {
+      if( postBoundaryHandlingVoidFunctions_[level].empty() )
+      {
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+         {
+            bool coarseNeighbors = false;
+            for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighbors; ++i )
+               coarseNeighbors = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+            if( coarseNeighbors )
+            {
+               startTiming( "boundary handling", level );
+               boundarySweepWithLayers_( *block );
+               stopTiming( "boundary handling", level );
+
+               for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+               {
+                  startTiming( func->second, level );
+                  (func->first)( *block, level, executionCount );
+                  stopTiming( func->second, level );
+               }
+
+               startTiming( "stream", level );
+               sweep_->stream( *block, StreamIncludedGhostLayers );
+               stopTiming( "stream", level );
+            }
+            else
+            {
+               startTiming( "boundary handling", level );
+               boundarySweep_( *block );
+               stopTiming( "boundary handling", level );
+
+               for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+               {
+                  startTiming( func->second, level );
+                  (func->first)( *block, level, executionCount );
+                  stopTiming( func->second, level );
+               }
+
+               startTiming( "stream", level );
+               sweep_->stream( *block, uint_t(0) );
+               stopTiming( "stream", level );
+            }
+            
+            if( doEqualLevelBorderStreamCorrection )
+            {
+               startTiming( "equal level border stream correction", level, "[prepare]" );
+               equalLevelBorderStreamCorrection_.prepare( *block );
+               stopTiming( "equal level border stream correction", level, "[prepare]" );
+            }
+         }
+      }
+      else
+      {
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+         {
+            bool coarseNeighbors = false;
+            for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighbors; ++i )
+               coarseNeighbors = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+            if( coarseNeighbors )
+            {
+               startTiming( "boundary handling", level );
+               boundarySweepWithLayers_( *block );
+               stopTiming( "boundary handling", level );
+            }
+            else
+            {
+               startTiming( "boundary handling", level );
+               boundarySweep_( *block );
+               stopTiming( "boundary handling", level );
+            }
+            for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+            {
+               startTiming( func->second, level );
+               (func->first)( *block, level, executionCount );
+               stopTiming( func->second, level );
+            }
+         }
+         for( auto func = postBoundaryHandlingVoidFunctions_[level].begin(); func != postBoundaryHandlingVoidFunctions_[level].end(); ++func )
+         {
+            startTiming( func->second, level );
+            (func->first)( level, executionCount );
+            stopTiming( func->second, level );
+         }
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+         {
+            bool coarseNeighbors = false;
+            for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighbors; ++i )
+               coarseNeighbors = (*block)->neighborhoodSectionHasLargerBlock(i);
+               
+            if( coarseNeighbors )
+            {
+               startTiming( "stream", level );
+               sweep_->stream( *block, StreamIncludedGhostLayers );
+               stopTiming( "stream", level );
+            }
+            else
+            {
+               startTiming( "stream", level );
+               sweep_->stream( *block, uint_t(0) );
+               stopTiming( "stream", level );
+            }
+            
+            if( doEqualLevelBorderStreamCorrection )
+            {
+               startTiming( "equal level border stream correction", level, "[prepare]" );
+               equalLevelBorderStreamCorrection_.prepare( *block );
+               stopTiming( "equal level border stream correction", level, "[prepare]" );
+            }
+         }
+      }
+   }
+   else
+   {
+      if( postBoundaryHandlingVoidFunctions_[level].empty() )
+      {
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+         {
+            bool coarseNeighbors = false;
+            for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighbors; ++i )
+               coarseNeighbors = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+            if( coarseNeighbors )
+            {
+               boundarySweepWithLayers_( *block );
+               for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+                  (func->first)( *block, level, executionCount );
+               sweep_->stream( *block, StreamIncludedGhostLayers );
+            }
+            else
+            {
+               boundarySweep_( *block );
+               for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+                  (func->first)( *block, level, executionCount );
+               sweep_->stream( *block, uint_t(0) );
+            }
+            
+            if( doEqualLevelBorderStreamCorrection )
+               equalLevelBorderStreamCorrection_.prepare( *block );
+         }
+      }
+      else
+      {
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+         {
+            bool coarseNeighbors = false;
+            for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighbors; ++i )
+               coarseNeighbors = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+            if( coarseNeighbors )
+            {
+               boundarySweepWithLayers_( *block );
+            }
+            else
+            {
+               boundarySweep_( *block );
+            }
+            for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+               (func->first)( *block, level, executionCount );
+         }
+         for( auto func = postBoundaryHandlingVoidFunctions_[level].begin(); func != postBoundaryHandlingVoidFunctions_[level].end(); ++func )
+            (func->first)( level, executionCount );
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+         {
+            bool coarseNeighbors = false;
+            for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighbors; ++i )
+               coarseNeighbors = (*block)->neighborhoodSectionHasLargerBlock(i);
+                     
+            if( coarseNeighbors )
+            {
+               sweep_->stream( *block, StreamIncludedGhostLayers );
+            }
+            else
+            {
+               sweep_->stream( *block, uint_t(0) );
+            }
+            
+            if( doEqualLevelBorderStreamCorrection )
+               equalLevelBorderStreamCorrection_.prepare( *block );            
+         }
+      }
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::finishStream( std::vector< Block * > & blocks, const uint_t level, const uint_t executionCount )
+{
+   auto _blocks = blocks_.lock();
+   WALBERLA_CHECK_NOT_NULLPTR( _blocks, "Trying to access 'TimeStep' (refinement) for a block storage object that doesn't exist anymore" );
+
+   const uint_t finestLevel = _blocks->getNumberOfLevels() - uint_t(1);
+   const bool doEqualLevelBorderStreamCorrection = ( performEqualLevelBorderStreamCorrection_ && level != finestLevel );
+
+   if( timing_ )
+   {
+      for( auto block = blocks.begin(); block != blocks.end(); ++block )
+      {
+         if( doEqualLevelBorderStreamCorrection )
+         {
+            startTiming( "equal level border stream correction", level, "[apply]" );
+            equalLevelBorderStreamCorrection_.apply( *block );
+            stopTiming( "equal level border stream correction", level, "[apply]" );
+         }
+         for( auto func = postStreamBlockFunctions_[level].begin(); func != postStreamBlockFunctions_[level].end(); ++func )
+         {
+            startTiming( func->second, level );
+            (func->first)( *block, level, executionCount );
+            stopTiming( func->second, level );
+         }
+      }
+      for( auto func = postStreamVoidFunctions_[level].begin(); func != postStreamVoidFunctions_[level].end(); ++func )
+      {
+         startTiming( func->second, level );
+         (func->first)( level, executionCount );
+         stopTiming( func->second, level );
+      }
+   }
+   else
+   {
+      for( auto block = blocks.begin(); block != blocks.end(); ++block )
+      {
+         if( doEqualLevelBorderStreamCorrection )
+            equalLevelBorderStreamCorrection_.apply( *block );
+            
+         for( auto func = postStreamBlockFunctions_[level].begin(); func != postStreamBlockFunctions_[level].end(); ++func )
+            (func->first)( *block, level, executionCount );
+      }
+      for( auto func = postStreamVoidFunctions_[level].begin(); func != postStreamVoidFunctions_[level].end(); ++func )
+         (func->first)( level, executionCount );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::streamCollide( std::vector< Block * > & blocks, const uint_t level, const uint_t executionCount )
+{
+#ifndef NDEBUG
+   auto _blocks = blocks_.lock();
+   WALBERLA_CHECK_NOT_NULLPTR( _blocks, "Trying to access 'TimeStep' (refinement) for a block storage object that doesn't exist anymore" );
+   WALBERLA_ASSERT_EQUAL( level, _blocks->getNumberOfLevels() - uint_t(1) );
+#endif
+
+   if( postStreamVoidFunctions_[level].empty() )
+   {
+      if( timing_ )
+      {
+         if( postBoundaryHandlingVoidFunctions_[level].empty() )
+         {
+            for( auto block = blocks.begin(); block != blocks.end(); ++block )
+            {
+               bool coarseNeighborsOrPostStreamFunctions = !(postStreamBlockFunctions_[level].empty());
+               for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighborsOrPostStreamFunctions; ++i )
+                  coarseNeighborsOrPostStreamFunctions = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+               if( coarseNeighborsOrPostStreamFunctions )
+               {
+                  startTiming( "boundary handling", level );
+                  boundarySweepWithLayers_( *block );
+                  stopTiming( "boundary handling", level );
+
+                  for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+                  {
+                     startTiming( func->second, level );
+                     (func->first)( *block, level, executionCount );
+                     stopTiming( func->second, level );
+                  }
+
+                  startTiming( "stream", level );
+                  sweep_->stream( *block, StreamIncludedGhostLayers );
+                  stopTiming( "stream", level );
+
+                  for( auto func = postStreamBlockFunctions_[level].begin(); func != postStreamBlockFunctions_[level].end(); ++func )
+                  {
+                     startTiming( func->second, level );
+                     (func->first)( *block, level, executionCount );
+                     stopTiming( func->second, level );
+                  }
+
+                  startTiming( "collide", level );
+                  sweep_->collide( *block );
+                  stopTiming( "collide", level );
+               }
+               else
+               {
+                  startTiming( "boundary handling", level );
+                  boundarySweep_( *block );
+                  stopTiming( "boundary handling", level );
+
+                  for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+                  {
+                     startTiming( func->second, level );
+                     (func->first)( *block, level, executionCount );
+                     stopTiming( func->second, level );
+                  }
+
+                  startTiming( "stream & collide", level );
+                  (*sweep_)( *block );
+                  stopTiming( "stream & collide", level );
+               }
+               for( auto func = postCollideBlockFunctions_[level].begin(); func != postCollideBlockFunctions_[level].end(); ++func )
+               {
+                  startTiming( func->second, level );
+                  (func->first)( *block, level, executionCount + uint_t(1) );
+                  stopTiming( func->second, level );
+               }
+            }
+            for( auto func = postCollideVoidFunctions_[level].begin(); func != postCollideVoidFunctions_[level].end(); ++func )
+            {
+               startTiming( func->second, level );
+               (func->first)( level, executionCount + uint_t(1) );
+               stopTiming( func->second, level );
+            }
+         }
+         else
+         {
+            for( auto block = blocks.begin(); block != blocks.end(); ++block )
+            {
+               bool coarseNeighbors = false;
+               for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighbors; ++i )
+                  coarseNeighbors = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+               if( coarseNeighbors )
+               {
+                  startTiming( "boundary handling", level );
+                  boundarySweepWithLayers_( *block );
+                  stopTiming( "boundary handling", level );
+               }
+               else
+               {
+                  startTiming( "boundary handling", level );
+                  boundarySweep_( *block );
+                  stopTiming( "boundary handling", level );
+               }
+               for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+               {
+                  startTiming( func->second, level );
+                  (func->first)( *block, level, executionCount );
+                  stopTiming( func->second, level );
+               }
+            }
+            for( auto func = postBoundaryHandlingVoidFunctions_[level].begin(); func != postBoundaryHandlingVoidFunctions_[level].end(); ++func )
+            {
+               startTiming( func->second, level );
+               (func->first)( level, executionCount );
+               stopTiming( func->second, level );
+            }
+            for( auto block = blocks.begin(); block != blocks.end(); ++block )
+            {
+               bool coarseNeighborsOrPostStreamFunctions = !(postStreamBlockFunctions_[level].empty());
+               for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighborsOrPostStreamFunctions; ++i )
+                  coarseNeighborsOrPostStreamFunctions = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+               if( coarseNeighborsOrPostStreamFunctions )
+               {
+                  startTiming( "stream", level );
+                  sweep_->stream( *block, StreamIncludedGhostLayers );
+                  stopTiming( "stream", level );
+
+                  for( auto func = postStreamBlockFunctions_[level].begin(); func != postStreamBlockFunctions_[level].end(); ++func )
+                  {
+                     startTiming( func->second, level );
+                     (func->first)( *block, level, executionCount );
+                     stopTiming( func->second, level );
+                  }
+
+                  startTiming( "collide", level );
+                  sweep_->collide( *block );
+                  stopTiming( "collide", level );
+               }
+               else
+               {
+                  startTiming( "stream & collide", level );
+                  (*sweep_)( *block );
+                  stopTiming( "stream & collide", level );
+               }
+               for( auto func = postCollideBlockFunctions_[level].begin(); func != postCollideBlockFunctions_[level].end(); ++func )
+               {
+                  startTiming( func->second, level );
+                  (func->first)( *block, level, executionCount + uint_t(1) );
+                  stopTiming( func->second, level );
+               }
+            }
+            for( auto func = postCollideVoidFunctions_[level].begin(); func != postCollideVoidFunctions_[level].end(); ++func )
+            {
+               startTiming( func->second, level );
+               (func->first)( level, executionCount + uint_t(1) );
+               stopTiming( func->second, level );
+            }
+         }
+      }
+      else
+      {
+         if( postBoundaryHandlingVoidFunctions_[level].empty() )
+         {
+            for( auto block = blocks.begin(); block != blocks.end(); ++block )
+            {
+               bool coarseNeighborsOrPostStreamFunctions = !(postStreamBlockFunctions_[level].empty());
+               for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighborsOrPostStreamFunctions; ++i )
+                  coarseNeighborsOrPostStreamFunctions = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+               if( coarseNeighborsOrPostStreamFunctions )
+               {
+                  boundarySweepWithLayers_( *block );
+                  for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+                     (func->first)( *block, level, executionCount );
+                  sweep_->stream( *block, StreamIncludedGhostLayers );
+                  for( auto func = postStreamBlockFunctions_[level].begin(); func != postStreamBlockFunctions_[level].end(); ++func )
+                     (func->first)( *block, level, executionCount );
+                  sweep_->collide( *block );
+               }
+               else
+               {
+                  boundarySweep_( *block );
+                  for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+                     (func->first)( *block, level, executionCount );
+                  (*sweep_)( *block );
+               }
+               for( auto func = postCollideBlockFunctions_[level].begin(); func != postCollideBlockFunctions_[level].end(); ++func )
+                  (func->first)( *block, level, executionCount + uint_t(1) );
+            }
+            for( auto func = postCollideVoidFunctions_[level].begin(); func != postCollideVoidFunctions_[level].end(); ++func )
+               (func->first)( level, executionCount + uint_t(1) );
+         }
+         else
+         {
+            for( auto block = blocks.begin(); block != blocks.end(); ++block )
+            {
+               bool coarseNeighbors = false;
+               for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighbors; ++i )
+                  coarseNeighbors = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+               if( coarseNeighbors )
+               {
+                  boundarySweepWithLayers_( *block );
+               }
+               else
+               {
+                  boundarySweep_( *block );
+               }
+               for( auto func = postBoundaryHandlingBlockFunctions_[level].begin(); func != postBoundaryHandlingBlockFunctions_[level].end(); ++func )
+                  (func->first)( *block, level, executionCount );
+            }
+            for( auto func = postBoundaryHandlingVoidFunctions_[level].begin(); func != postBoundaryHandlingVoidFunctions_[level].end(); ++func )
+               (func->first)( level, executionCount );
+            for( auto block = blocks.begin(); block != blocks.end(); ++block )
+            {
+               bool coarseNeighborsOrPostStreamFunctions = !(postStreamBlockFunctions_[level].empty());
+               for( uint_t i = uint_t(0); i < uint_t(26) && !coarseNeighborsOrPostStreamFunctions; ++i )
+                  coarseNeighborsOrPostStreamFunctions = (*block)->neighborhoodSectionHasLargerBlock(i);
+
+               if( coarseNeighborsOrPostStreamFunctions )
+               {
+                  sweep_->stream( *block, StreamIncludedGhostLayers );
+                  for( auto func = postStreamBlockFunctions_[level].begin(); func != postStreamBlockFunctions_[level].end(); ++func )
+                     (func->first)( *block, level, executionCount );
+                  sweep_->collide( *block );
+               }
+               else
+               {
+                  (*sweep_)( *block );
+               }
+               for( auto func = postCollideBlockFunctions_[level].begin(); func != postCollideBlockFunctions_[level].end(); ++func )
+                  (func->first)( *block, level, executionCount + uint_t(1) );
+            }
+            for( auto func = postCollideVoidFunctions_[level].begin(); func != postCollideVoidFunctions_[level].end(); ++func )
+               (func->first)( level, executionCount + uint_t(1) );
+         }
+      }
+   }
+   else
+   {
+      stream( blocks, level, executionCount );
+
+      if( timing_ )
+      {
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+         {
+            for( auto func = postStreamBlockFunctions_[level].begin(); func != postStreamBlockFunctions_[level].end(); ++func )
+            {
+               startTiming( func->second, level );
+               (func->first)( *block, level, executionCount );
+               stopTiming( func->second, level );
+            }
+         }
+         for( auto func = postStreamVoidFunctions_[level].begin(); func != postStreamVoidFunctions_[level].end(); ++func )
+         {
+            startTiming( func->second, level );
+            (func->first)( level, executionCount );
+            stopTiming( func->second, level );
+         }
+      }
+      else
+      {
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+         {
+            for( auto func = postStreamBlockFunctions_[level].begin(); func != postStreamBlockFunctions_[level].end(); ++func )
+               (func->first)( *block, level, executionCount );
+         }
+         for( auto func = postStreamVoidFunctions_[level].begin(); func != postStreamVoidFunctions_[level].end(); ++func )
+            (func->first)( level, executionCount );
+      }
+
+      collide( blocks, level, executionCount + uint_t(1) );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::startCommunicationEqualLevel( const uint_t level )
+{
+   if( timing_ )
+   {
+      startTiming( "communication equal level", level, "[pack & send]" );
+      communication_.startCommunicateEqualLevel( level );
+      stopTiming( "communication equal level", level, "[pack & send]" );
+   }
+   else
+   {
+      communication_.startCommunicateEqualLevel( level );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::endCommunicationEqualLevel( const uint_t level )
+{
+   if( timing_ )
+   {
+      startTiming( "communication equal level", level, "[wait & unpack]" );
+      communication_.waitCommunicateEqualLevel( level );
+      stopTiming( "communication equal level", level, "[wait & unpack]" );
+   }
+   else
+   {
+      communication_.waitCommunicateEqualLevel( level );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::startCommunicationCoarseToFine( const uint_t level )
+{
+   if( timing_ )
+   {
+      startTiming( "communication coarse to fine", level, "[pack & send]" );
+      communication_.startCommunicateCoarseToFine( level );
+      stopTiming( "communication coarse to fine", level, "[pack & send]" );
+   }
+   else
+   {
+      communication_.startCommunicateCoarseToFine( level );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::endCommunicationCoarseToFine( const uint_t level )
+{
+   if( timing_ )
+   {
+      startTiming( "communication coarse to fine", level, "[wait & unpack]" );
+      communication_.waitCommunicateCoarseToFine( level );
+      stopTiming( "communication coarse to fine", level, "[wait & unpack]" );
+   }
+   else
+   {
+      communication_.waitCommunicateCoarseToFine( level );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::startCommunicationFineToCoarse( const uint_t level )
+{
+   if( timing_ )
+   {
+      startTiming( "communication fine to coarse", level, "[pack & send]" );
+      communication_.startCommunicateFineToCoarse( level );
+      stopTiming( "communication fine to coarse", level, "[pack & send]" );
+   }
+   else
+   {
+      communication_.startCommunicateFineToCoarse( level );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::endCommunicationFineToCoarse( const uint_t level )
+{
+   if( timing_ )
+   {
+      startTiming( "communication fine to coarse", level, "[wait & unpack]" );
+      communication_.waitCommunicateFineToCoarse( level );
+      stopTiming( "communication fine to coarse", level, "[wait & unpack]" );
+   }
+   else
+   {
+      communication_.waitCommunicateFineToCoarse( level );
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::performLinearExplosion( std::vector< Block * > & blocks, const uint_t level )
+{
+   if( performLinearExplosion_ )
+   {
+      if( timing_ )
+      {
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+         {
+            startTiming( "linear explosion", level );
+            linearExplosion_( *block );
+            stopTiming( "linear explosion", level );
+         }
+      }
+      else
+      {
+         for( auto block = blocks.begin(); block != blocks.end(); ++block )
+            linearExplosion_( *block );
+      }
+   }
+}
+
+
+
+template< typename LatticeModel_T, typename Sweep_T, typename BoundaryHandling_T >
+void TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T >::recursiveStep( const uint_t level, const uint_t executionCount )
+{
+   // -> "A generic, mass conservative local grid refinement technique for lattice-Boltzmann schemes", Rhode et al., 2005
+
+   auto _blocks = blocks_.lock();
+   WALBERLA_CHECK_NOT_NULLPTR( _blocks, "Trying to access 'TimeStep' (refinement) for a block storage object that doesn't exist anymore" );
+
+   const uint_t coarsestLevel = uint_t(0);
+   const uint_t   finestLevel = _blocks->getNumberOfLevels() - uint_t(1);
+
+   const uint_t executionCount1st = (executionCount + uint_t(1)) * uint_t(2) - uint_t(2);
+   const uint_t executionCount2nd = (executionCount + uint_t(1)) * uint_t(2) - uint_t(1);
+
+   std::vector< Block * > blocks = selectedBlocks( level );
+
+   WALBERLA_LOG_DETAIL("Starting recursive step with level " << level << " and execution count " << executionCount);
+
+   if( asynchronousCommunication_ && level != coarsestLevel )
+   {
+      WALBERLA_LOG_DETAIL("Start communication coarse to fine, initiated by fine level " << level );
+      startCommunicationCoarseToFine( level ); // [start] explosion (initiated by fine level, involves "level" and "level-1")
+   }
+
+   WALBERLA_LOG_DETAIL("Colliding on level " << level);
+   collide( blocks, level, executionCount1st );
+
+   if( asynchronousCommunication_ )
+   {
+      WALBERLA_LOG_DETAIL("Start communication equal level, initiated by level " << level );
+      startCommunicationEqualLevel( level ); // [start] equal level communication
+   }
+
+   if( level != finestLevel )
+   {
+      WALBERLA_LOG_DETAIL("Calling recursive step with level " << level + uint_t(1) << " and execution count " << executionCount1st );
+      recursiveStep( level + uint_t(1), executionCount1st );
+
+      if( asynchronousCommunication_ ) {
+         WALBERLA_LOG_DETAIL("Start communication fine to coarse, initiated by coarse level " << level );
+         startCommunicationFineToCoarse(level + uint_t(1)); // [start] coalescence (initiated by coarse level)
+      }
+   }
+
+   if( level != coarsestLevel )
+   {
+      if( !asynchronousCommunication_ ) {
+         WALBERLA_LOG_DETAIL("Start communication coarse to fine, initiated by fine level " << level );
+         startCommunicationCoarseToFine(level); // [start] explosion (initiated by fine level, involves "level" and "level-1")
+      }
+      WALBERLA_LOG_DETAIL("End communication coarse to fine, initiated by fine level " << level );
+      endCommunicationCoarseToFine( level ); // [end] explosion (initiated by fine level, involves "level" and "level-1")
+      WALBERLA_LOG_DETAIL("Perform linear explosion on level " << level );
+      performLinearExplosion( blocks, level );
+   }
+
+   if( !asynchronousCommunication_ ) {
+      WALBERLA_LOG_DETAIL("Start communication equal level, initiated by level " << level );
+      startCommunicationEqualLevel(level); // [start] equal level communication
+   }
+   WALBERLA_LOG_DETAIL("End communication equal level, initiated by level " << level );
+   endCommunicationEqualLevel( level ); // [end] equal level communication
+
+   // performLinearExplosion( blocks, level ); // if equal level neighbor values are needed, linear explosion should be performed here
+
+   if( level == finestLevel && level != coarsestLevel )
+   {
+      WALBERLA_LOG_DETAIL("Stream + collide on level " << level );
+      streamCollide( blocks, level, executionCount1st );
+   }
+   else
+   {
+      WALBERLA_LOG_DETAIL("Stream on level " << level );
+      stream( blocks, level, executionCount1st );
+
+      if( level != finestLevel )
+      {
+         if( !asynchronousCommunication_ ) {
+            WALBERLA_LOG_DETAIL("Start communication fine to coarse, initiated by coarse level " << level );
+            startCommunicationFineToCoarse(level + uint_t(1)); // [start] coalescence (initiated by coarse level)
+         }
+         WALBERLA_LOG_DETAIL("End communication fine to coarse, initiated by coarse level " << level );
+         endCommunicationFineToCoarse( level + uint_t(1) ); // [end] coalescence (initiated by coarse level)
+      }
+
+      WALBERLA_LOG_DETAIL("Finish stream on level " << level );
+      finishStream( blocks, level, executionCount1st );
+
+      if( level == coarsestLevel )
+      {
+         WALBERLA_LOG_DETAIL("End recursive step on level " << level );
+         return;
+      }
+
+      WALBERLA_LOG_DETAIL("Colliding on level " << level);
+      collide( blocks, level, executionCount2nd );
+   }
+
+   if( asynchronousCommunication_ ) {
+      WALBERLA_LOG_DETAIL("Start communication equal level, initiated by level " << level );
+      startCommunicationEqualLevel(level); // [start] equal level communication
+   }
+
+   if( level != finestLevel )
+   {
+      WALBERLA_LOG_DETAIL("Calling recursive step with level " << level + uint_t(1) << " and execution count " << executionCount2nd );
+      recursiveStep( level + uint_t(1), executionCount2nd );
+   
+      if( asynchronousCommunication_ ) {
+         WALBERLA_LOG_DETAIL("Start communication fine to coarse, initiated by coarse level " << level );
+         startCommunicationFineToCoarse(level + uint_t(1)); // [start] coalescence (initiated by coarse level)
+      }
+   }
+
+   if( !asynchronousCommunication_ ) {
+      WALBERLA_LOG_DETAIL("Start communication equal level, initiated by level " << level );
+      startCommunicationEqualLevel(level); // [start] equal level communication
+   }
+   WALBERLA_LOG_DETAIL("End communication equal level, initiated by level " << level );
+   endCommunicationEqualLevel( level ); // [end] equal level communication
+
+   WALBERLA_LOG_DETAIL("Stream on level " << level );
+   stream( blocks, level, executionCount2nd );
+
+   if( level != finestLevel )
+   {
+      if( !asynchronousCommunication_ )
+      {
+         WALBERLA_LOG_DETAIL("Start communication fine to coarse, initiated by coarse level " << level );
+         startCommunicationFineToCoarse( level + uint_t(1) ); // [start] coalescence (initiated by coarse level)
+      }
+      WALBERLA_LOG_DETAIL("End communication fine to coarse, initiated by coarse level " << level );
+      endCommunicationFineToCoarse( level + uint_t(1) ); // [end] coalescence (initiated by coarse level)
+   }
+
+   WALBERLA_LOG_DETAIL("Finish stream on level " << level );
+   finishStream( blocks, level, executionCount2nd );
+
+   WALBERLA_LOG_DETAIL("End recursive step on level " << level );
+}
+
+
+
+
+template< typename LatticeModel_T, typename BoundaryHandling_T, typename Sweep_T >
+shared_ptr< TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T > >
+makeTimeStep( weak_ptr< StructuredBlockForest > blocks, shared_ptr< Sweep_T > & sweep,
+              const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId,
+              const Set<SUID> & requiredBlockSelectors = Set<SUID>::emptySet(),
+              const Set<SUID> & incompatibleBlockSelectors = Set<SUID>::emptySet() )
+{
+   typedef TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T > TS_T;
+   return shared_ptr< TS_T >( new TS_T( blocks, sweep, pdfFieldId, boundaryHandlingId, requiredBlockSelectors, incompatibleBlockSelectors ) );
+}
+
+
+
+template< typename LatticeModel_T, typename BoundaryHandling_T, typename Sweep_T >
+shared_ptr< TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T > >
+makeTimeStep( weak_ptr< StructuredBlockForest > blocks, shared_ptr< Sweep_T > & sweep,
+              const BlockDataID & pdfFieldId, const BlockDataID & boundaryHandlingId,
+              const shared_ptr< TimeStepPdfPackInfo > & pdfPackInfo,
+              const Set<SUID> & requiredBlockSelectors = Set<SUID>::emptySet(),
+              const Set<SUID> & incompatibleBlockSelectors = Set<SUID>::emptySet() )
+{
+   typedef TimeStep< LatticeModel_T, Sweep_T, BoundaryHandling_T > TS_T;
+   return shared_ptr< TS_T >( new TS_T( blocks, sweep, pdfFieldId, boundaryHandlingId, pdfPackInfo, requiredBlockSelectors, incompatibleBlockSelectors ) );
+}
+
+
+
+} // namespace refinement
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/TimeStepPdfPackInfo.h b/src/lbm/refinement_rebase/TimeStepPdfPackInfo.h
new file mode 100644
index 000000000..d4749b882
--- /dev/null
+++ b/src/lbm/refinement_rebase/TimeStepPdfPackInfo.h
@@ -0,0 +1,44 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file TimeStep.h
+//! \ingroup lbm
+//! \author Christian Godenschwager <christian.godenschwager@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "blockforest/communication/NonUniformPackInfo.h"
+
+namespace walberla {
+namespace lbm {
+namespace refinement {
+
+
+class TimeStepPdfPackInfo : public blockforest::communication::NonUniformPackInfo
+{
+public:
+   virtual bool optimizedEqualLevelCommunication() const = 0;
+   virtual void optimizeEqualLevelCommunication( const bool value = true ) = 0;
+
+   virtual bool optimizedForLinearExplosion() const = 0;
+   virtual void optimizeForLinearExplosion( const bool value = true ) = 0;
+};
+
+
+} // namespace refinement
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/TimeTracker.h b/src/lbm/refinement_rebase/TimeTracker.h
new file mode 100644
index 000000000..3cba8dd52
--- /dev/null
+++ b/src/lbm/refinement_rebase/TimeTracker.h
@@ -0,0 +1,109 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of
+//  the License, or (at your option) any later version.
+//
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file BlockSweepWrapper.h
+//! \ingroup lbm
+//! \author Florian Schornbaum <florian.schornbaum@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/DataTypes.h"
+#include "core/debug/Debug.h"
+
+
+namespace walberla {
+namespace lbm {
+
+
+
+/// Keeps track of the simulation time (One step on the coarsest level counts as '1', one step on the
+/// next finer level counts as '0.5', one step on the next level as '0.25' etc.)
+class TimeTracker
+{
+public:
+
+   TimeTracker( const uint_t initialTime = uint_t(0) )
+   {
+      fractionCounter_.push_back( initialTime );
+      fractionBase_.push_back( uint_t(1) );
+      time_.push_back( real_c( initialTime ) );
+   }
+
+   // for use without refinement time step
+   void operator()()
+   {
+      WALBERLA_ASSERT_GREATER( fractionCounter_.size(), 0 );
+      WALBERLA_ASSERT_GREATER( fractionBase_.size(), 0 );
+      WALBERLA_ASSERT_GREATER( time_.size(), 0 );
+      fractionCounter_[0] += uint_t(1);
+      time_[0] = real_c( fractionCounter_[0] );
+   }
+
+   // for use with refinement time step (addPost*VoidFunction)
+#ifdef NDEBUG
+   void operator()( const uint_t level, const uint_t )
+#else
+   void operator()( const uint_t level, const uint_t executionCount )
+#endif
+   {
+      checkLevel( level );
+
+      WALBERLA_ASSERT_EQUAL( fractionCounter_[ level ] % fractionBase_[ level ], executionCount );
+      fractionCounter_[ level ] += uint_t(1);
+
+      time_[ level ] = real_c( fractionCounter_[ level ] ) / real_c( fractionBase_[ level ] );
+   }
+
+   real_t getTime( const uint_t level = uint_t(0) )
+   {
+      checkLevel( level );
+
+      return time_[ level ];
+   }
+
+private:
+
+   void checkLevel( const uint_t level )
+   {
+      if( level >= time_.size() )
+      {
+         const uint_t previousSize = time_.size();
+
+         fractionCounter_.resize( level + uint_t(1) );
+         fractionBase_.resize( level + uint_t(1) );
+         time_.resize( level + uint_t(1) );
+
+         for( uint_t i = previousSize; i <= level; ++i )
+         {
+            fractionCounter_[ i ] = fractionCounter_[0] * math::uintPow2( i );
+            fractionBase_[ i ] = math::uintPow2( i );
+            time_[ i ] = time_[0];
+         }
+      }
+   }
+
+   std::vector< uint_t > fractionCounter_;
+   std::vector< uint_t > fractionBase_;
+
+   std::vector< real_t > time_;
+
+}; // class TimeTracker
+
+
+
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/VorticityBasedLevelDetermination.h b/src/lbm/refinement_rebase/VorticityBasedLevelDetermination.h
new file mode 100644
index 000000000..c3f4ee20d
--- /dev/null
+++ b/src/lbm/refinement_rebase/VorticityBasedLevelDetermination.h
@@ -0,0 +1,161 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file VorticityBasedLevelDetermination.h
+//! \ingroup lbm
+//! \author Florian Schornbaum <florian.schornbaum@fau.de>
+//! \author Christoph Rettinger <christoph.rettinger@fau.de>
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "blockforest/BlockForest.h"
+#include "core/math/Vector3.h"
+#include "domain_decomposition/BlockDataID.h"
+#include "field/GhostLayerField.h"
+
+#include <vector>
+
+namespace walberla {
+namespace lbm {
+namespace refinement {
+
+
+/*!\brief Level determination for refinement check based on (scaled) vorticity values
+ *
+ * If (scaled) vorticity magnitude is below lowerLimit in all cells of a block, that block could be coarsened.
+ * If the (scaled) vorticity value is above the upperLimit for at least one cell, that block gets marked for refinement.
+ * Else, the block remains on the current level.
+ *
+ * The scaling originates from neglecting the actual mesh size on the block to obtain different vorticity values for
+ * different mesh sizes.
+ */
+template< typename Filter_T, bool Pseudo2D = false >
+class VorticityBasedLevelDetermination // used as a 'BlockForest::RefreshMinTargetLevelDeterminationFunction'
+{
+
+public:
+
+   typedef GhostLayerField< Vector3<real_t>, 1 >  VectorField_T;
+
+   VorticityBasedLevelDetermination( const ConstBlockDataID & fieldID, const Filter_T & filter,
+                                     const real_t upperLimit, const real_t lowerLimit, const uint_t maxLevel ) :
+         fieldID_( fieldID ), filter_( filter ),
+         upperLimit_( upperLimit * upperLimit ), lowerLimit_( lowerLimit * lowerLimit ), maxLevel_( maxLevel )
+   {}
+
+   void operator()( std::vector< std::pair< const Block *, uint_t > > & minTargetLevels,
+                    std::vector< const Block * > & blocksAlreadyMarkedForRefinement,
+                    const BlockForest & forest );
+
+private:
+
+   ConstBlockDataID fieldID_;
+
+   Filter_T filter_;
+
+   real_t upperLimit_;
+   real_t lowerLimit_;
+
+   uint_t maxLevel_;
+
+}; // class VorticityRefinement
+
+template< typename Filter_T, bool Pseudo2D >
+void VorticityBasedLevelDetermination< Filter_T, Pseudo2D >::operator()( std::vector< std::pair< const Block *, uint_t > > & minTargetLevels,
+                                                                         std::vector< const Block * > &, const BlockForest & )
+{
+   for( auto it = minTargetLevels.begin(); it != minTargetLevels.end(); ++it )
+   {
+      const Block * const block = it->first;
+      const VectorField_T * u = block->template getData< VectorField_T >( fieldID_ );
+
+      if( u == nullptr )
+      {
+         it->second = uint_t(0);
+         continue;
+      }
+
+      WALBERLA_ASSERT_GREATER_EQUAL(u->nrOfGhostLayers(), uint_t(1));
+
+      CellInterval interval = u->xyzSize();
+      Cell expand( cell_idx_c(-1), cell_idx_c(-1), Pseudo2D ? cell_idx_t(0) : cell_idx_c(-1) );
+      interval.expand( expand );
+
+      const cell_idx_t one( cell_idx_t(1) );
+      const real_t half( real_c(0.5) );
+
+      bool refine( false );
+      bool coarsen( true );
+
+      filter_( *block );
+
+      const cell_idx_t xSize = cell_idx_c( interval.xSize() );
+      const cell_idx_t ySize = cell_idx_c( interval.ySize() );
+      const cell_idx_t zSize = cell_idx_c( interval.zSize() );
+
+      for( cell_idx_t z = cell_idx_t(0); z < zSize; ++z ) {
+         for (cell_idx_t y = cell_idx_t(0); y < ySize; ++y) {
+            for (cell_idx_t x = cell_idx_t(0); x < xSize; ++x) {
+               if( filter_(x,y,z) && filter_(x+one,y,z) && filter_(x-one,y,z) && filter_(x,y+one,z) && filter_(x,y-one,z) &&
+                   ( Pseudo2D || (filter_(x,y,z+one) && filter_(x,y,z-one)) ) )
+               {
+                  const Vector3< real_t > xa = u->get(x+one,y,z);
+                  const Vector3< real_t > xb = u->get(x-one,y,z);
+                  const Vector3< real_t > ya = u->get(x,y+one,z);
+                  const Vector3< real_t > yb = u->get(x,y-one,z);
+                  const Vector3< real_t > za = Pseudo2D ? Vector3< real_t >(0) : u->get(x,y,z+one);
+                  const Vector3< real_t > zb = Pseudo2D ? Vector3< real_t >(0) : u->get(x,y,z-one);
+
+                  const real_t duzdy = half * ( ya[2] - yb[2] );
+                  const real_t duydz = half * ( za[1] - zb[1] );
+                  const real_t duxdz = half * ( za[0] - zb[0] );
+                  const real_t duzdx = half * ( xa[2] - xb[2] );
+                  const real_t duydx = half * ( xa[1] - xb[1] );
+                  const real_t duxdy = half * ( ya[0] - yb[0] );
+
+                  // since no dx was used in the gradient calculation, this value is actually curl * dx
+                  // this is needed to have changing curl values on different grid levels
+                  const Vector3< real_t > curl( duzdy - duydz, duxdz - duzdx, duydx - duxdy );
+                  const auto curlSqr = curl.sqrLength();
+
+                  if( curlSqr > lowerLimit_ )
+                  {
+                     coarsen = false;
+                     if( curlSqr > upperLimit_ )
+                        refine = true;
+                  }
+               }
+            }
+         }
+      }
+
+      if( refine && block->getLevel() < maxLevel_ )
+      {
+         WALBERLA_ASSERT( !coarsen );
+         it->second = block->getLevel() + uint_t(1);
+      }
+      if( coarsen && block->getLevel() > uint_t(0) )
+      {
+         WALBERLA_ASSERT( !refine );
+         it->second = block->getLevel() - uint_t(1);
+      }
+   }
+}
+
+} // namespace refinement
+} // namespace lbm
+} // namespace walberla
diff --git a/src/lbm/refinement_rebase/all.h b/src/lbm/refinement_rebase/all.h
new file mode 100644
index 000000000..a63a1ba38
--- /dev/null
+++ b/src/lbm/refinement_rebase/all.h
@@ -0,0 +1,32 @@
+//======================================================================================================================
+//
+//  This file is part of waLBerla. waLBerla is free software: you can 
+//  redistribute it and/or modify it under the terms of the GNU General Public
+//  License as published by the Free Software Foundation, either version 3 of 
+//  the License, or (at your option) any later version.
+//  
+//  waLBerla 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 General Public License along
+//  with waLBerla (see COPYING.txt). If not, see <http://www.gnu.org/licenses/>.
+//
+//! \file all.h
+//! \ingroup lbm
+//! \author Christian Godenschwager <christian.godenschwager@fau.de>
+//! \brief Collective header file for module lbm
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "BoundarySetup.h"
+#include "PdfFieldPackInfo.h"
+#include "PdfFieldSyncPackInfo.h"
+#include "RefinementFunctorWrapper.h"
+#include "TimeStep.h"
+#include "TimeStepPdfPackInfo.h"
+#include "TimeTracker.h"
+#include "VorticityBasedLevelDetermination.h"
diff --git a/tests/lbm/CMakeLists.txt b/tests/lbm/CMakeLists.txt
index f66bd62d0..4cc28ab9a 100644
--- a/tests/lbm/CMakeLists.txt
+++ b/tests/lbm/CMakeLists.txt
@@ -92,7 +92,8 @@ waLBerla_generate_target_from_python(NAME FieldLayoutAndVectorizationTestGenerat
 waLBerla_compile_test( FILES codegen/FieldLayoutAndVectorizationTest.cpp DEPENDS FieldLayoutAndVectorizationTestGenerated)
 
 
-set( configs D3Q19_SRT_INCOMP D3Q19_SRT_COMP D3Q27_SRT_INCOMP D3Q27_SRT_COMP D3Q27_TRT_INCOMP D3Q19_MRT_COMP )
+# set( configs D3Q19_SRT_INCOMP D3Q19_SRT_COMP D3Q27_SRT_INCOMP D3Q27_SRT_COMP D3Q27_TRT_INCOMP D3Q19_MRT_COMP )
+set( configs D3Q19_SRT_INCOMP )
 list( APPEND RefinementTargets "" )
 
 foreach( config ${configs} )
diff --git a/tests/lbm/codegen/CodeGenerationRefinement.cpp b/tests/lbm/codegen/CodeGenerationRefinement.cpp
index 47508ab70..fa728c748 100644
--- a/tests/lbm/codegen/CodeGenerationRefinement.cpp
+++ b/tests/lbm/codegen/CodeGenerationRefinement.cpp
@@ -42,19 +42,20 @@
 #include <lbm/field/PdfField.h>
 #include <lbm/lattice_model/D3Q19.h>
 #include <lbm/lattice_model/D3Q27.h>
-#include <lbm/refinement/TimeStep.h>
 #include <lbm/sweeps/CellwiseSweep.h>
 #include <timeloop/SweepTimeloop.h>
 #include <type_traits>
 
-#include "CodeGenerationRefinement_D3Q19_SRT_COMP_LatticeModel.h"
+#include <lbm/refinement_rebase/TimeStep.h>
+
+// #include "CodeGenerationRefinement_D3Q19_SRT_COMP_LatticeModel.h"
 #include "CodeGenerationRefinement_D3Q19_SRT_INCOMP_LatticeModel.h"
-#include "CodeGenerationRefinement_D3Q27_SRT_COMP_LatticeModel.h"
-#include "CodeGenerationRefinement_D3Q27_SRT_INCOMP_LatticeModel.h"
-#include "CodeGenerationRefinement_D3Q27_TRT_INCOMP_LatticeModel.h"
-#include "CodeGenerationRefinement_D3Q19_MRT_COMP_LatticeModel.h"
+// #include "CodeGenerationRefinement_D3Q27_SRT_COMP_LatticeModel.h"
+// #include "CodeGenerationRefinement_D3Q27_SRT_INCOMP_LatticeModel.h"
+// #include "CodeGenerationRefinement_D3Q27_TRT_INCOMP_LatticeModel.h"
+// #include "CodeGenerationRefinement_D3Q19_MRT_COMP_LatticeModel.h"
 
-//#define TEST_USES_VTK_OUTPUT
+#define TEST_USES_VTK_OUTPUT
 #ifdef TEST_USES_VTK_OUTPUT
 #include "lbm/vtk/Density.h"
 #include "lbm/vtk/Velocity.h"
@@ -503,13 +504,13 @@ int main( int argc, char ** argv )
 
    fieldIds.emplace_back();
 
-   typedef lbm::D3Q19<SRT, true>                                      walberalLM_D3Q19_SRT_COMP;
-   typedef lbm::CodeGenerationRefinement_D3Q19_SRT_COMP_LatticeModel     lbmpyLM_D3Q19_SRT_COMP;
-
-   AddRefinementTimeStep< walberalLM_D3Q19_SRT_COMP >::add(blocks, timeloop, walberalLM_D3Q19_SRT_COMP(SRT(GlobalOmega)),
-                                                           fieldIds.back(), field::fzyx, flagFieldId, velocity, "( waLBerla D3Q19 SRT   COMP )");
-   AddRefinementTimeStep<    lbmpyLM_D3Q19_SRT_COMP >::add(blocks, timeloop,  lbmpyLM_D3Q19_SRT_COMP(GlobalOmega),
-                                                           fieldIds.back(), field::fzyx, flagFieldId, velocity, "(   lbmpy  D3Q19 SRT   COMP )");
+//   typedef lbm::D3Q19<SRT, true>                                      walberalLM_D3Q19_SRT_COMP;
+//   typedef lbm::CodeGenerationRefinement_D3Q19_SRT_COMP_LatticeModel     lbmpyLM_D3Q19_SRT_COMP;
+//
+//   AddRefinementTimeStep< walberalLM_D3Q19_SRT_COMP >::add(blocks, timeloop, walberalLM_D3Q19_SRT_COMP(SRT(GlobalOmega)),
+//                                                           fieldIds.back(), field::fzyx, flagFieldId, velocity, "( waLBerla D3Q19 SRT   COMP )");
+//   AddRefinementTimeStep<    lbmpyLM_D3Q19_SRT_COMP >::add(blocks, timeloop,  lbmpyLM_D3Q19_SRT_COMP(GlobalOmega),
+//                                                           fieldIds.back(), field::fzyx, flagFieldId, velocity, "(   lbmpy  D3Q19 SRT   COMP )");
 
    ////////////////////////////////
    /// D3Q27 SRT incompressible ///
@@ -517,13 +518,13 @@ int main( int argc, char ** argv )
 
    fieldIds.emplace_back();
 
-   typedef lbm::D3Q27<SRT, false>                                       walberalLM_D3Q27_SRT_INCOMP;
-   typedef lbm::CodeGenerationRefinement_D3Q27_SRT_INCOMP_LatticeModel     lbmpyLM_D3Q27_SRT_INCOMP;
-
-   AddRefinementTimeStep< walberalLM_D3Q27_SRT_INCOMP >::add(blocks, timeloop, walberalLM_D3Q27_SRT_INCOMP(SRT(GlobalOmega)),
-                                                             fieldIds.back(), field::fzyx, flagFieldId, velocity, "( waLBerla D3Q27 SRT INCOMP )");
-   AddRefinementTimeStep<    lbmpyLM_D3Q27_SRT_INCOMP >::add(blocks, timeloop,  lbmpyLM_D3Q27_SRT_INCOMP(GlobalOmega),
-                                                             fieldIds.back(), field::fzyx, flagFieldId, velocity, "(   lbmpy  D3Q27 SRT INCOMP )");
+//   typedef lbm::D3Q27<SRT, false>                                       walberalLM_D3Q27_SRT_INCOMP;
+//   typedef lbm::CodeGenerationRefinement_D3Q27_SRT_INCOMP_LatticeModel     lbmpyLM_D3Q27_SRT_INCOMP;
+//
+//   AddRefinementTimeStep< walberalLM_D3Q27_SRT_INCOMP >::add(blocks, timeloop, walberalLM_D3Q27_SRT_INCOMP(SRT(GlobalOmega)),
+//                                                             fieldIds.back(), field::fzyx, flagFieldId, velocity, "( waLBerla D3Q27 SRT INCOMP )");
+//   AddRefinementTimeStep<    lbmpyLM_D3Q27_SRT_INCOMP >::add(blocks, timeloop,  lbmpyLM_D3Q27_SRT_INCOMP(GlobalOmega),
+//                                                             fieldIds.back(), field::fzyx, flagFieldId, velocity, "(   lbmpy  D3Q27 SRT INCOMP )");
 
    ////////////////////////////////
    /// D3Q27 SRT   compressible ///
@@ -531,13 +532,13 @@ int main( int argc, char ** argv )
 
    fieldIds.emplace_back();
 
-   typedef lbm::D3Q27<SRT, true>                                      walberalLM_D3Q27_SRT_COMP;
-   typedef lbm::CodeGenerationRefinement_D3Q27_SRT_COMP_LatticeModel     lbmpyLM_D3Q27_SRT_COMP;
-
-   AddRefinementTimeStep< walberalLM_D3Q27_SRT_COMP >::add(blocks, timeloop, walberalLM_D3Q27_SRT_COMP(SRT(GlobalOmega)),
-                                                             fieldIds.back(), field::fzyx, flagFieldId, velocity, "( waLBerla D3Q27 SRT   COMP )");
-   AddRefinementTimeStep<    lbmpyLM_D3Q27_SRT_COMP >::add(blocks, timeloop,  lbmpyLM_D3Q27_SRT_COMP(GlobalOmega),
-                                                             fieldIds.back(), field::fzyx, flagFieldId, velocity, "(   lbmpy  D3Q27 SRT   COMP )");
+//   typedef lbm::D3Q27<SRT, true>                                      walberalLM_D3Q27_SRT_COMP;
+//   typedef lbm::CodeGenerationRefinement_D3Q27_SRT_COMP_LatticeModel     lbmpyLM_D3Q27_SRT_COMP;
+//
+//   AddRefinementTimeStep< walberalLM_D3Q27_SRT_COMP >::add(blocks, timeloop, walberalLM_D3Q27_SRT_COMP(SRT(GlobalOmega)),
+//                                                             fieldIds.back(), field::fzyx, flagFieldId, velocity, "( waLBerla D3Q27 SRT   COMP )");
+//   AddRefinementTimeStep<    lbmpyLM_D3Q27_SRT_COMP >::add(blocks, timeloop,  lbmpyLM_D3Q27_SRT_COMP(GlobalOmega),
+//                                                             fieldIds.back(), field::fzyx, flagFieldId, velocity, "(   lbmpy  D3Q27 SRT   COMP )");
 
    /////////////////////
    /// RUN TEST RUNS ///
@@ -554,14 +555,14 @@ int main( int argc, char ** argv )
    //////////////////
 
    check<walberalLM_D3Q19_SRT_INCOMP, lbmpyLM_D3Q19_SRT_INCOMP>( blocks, fieldIds[0][0], fieldIds[0][1] );
-   check<walberalLM_D3Q19_SRT_COMP,   lbmpyLM_D3Q19_SRT_COMP>  ( blocks, fieldIds[1][0], fieldIds[1][1] );
+   // check<walberalLM_D3Q19_SRT_COMP,   lbmpyLM_D3Q19_SRT_COMP>  ( blocks, fieldIds[1][0], fieldIds[1][1] );
 
    //////////////////
    /// D3Q27, SRT ///
    //////////////////
 
-   check<walberalLM_D3Q27_SRT_INCOMP, lbmpyLM_D3Q27_SRT_INCOMP>( blocks, fieldIds[2][0], fieldIds[2][1] );
-   check<walberalLM_D3Q27_SRT_COMP,   lbmpyLM_D3Q27_SRT_COMP>  ( blocks, fieldIds[3][0], fieldIds[3][1] );
+   // check<walberalLM_D3Q27_SRT_INCOMP, lbmpyLM_D3Q27_SRT_INCOMP>( blocks, fieldIds[2][0], fieldIds[2][1] );
+   // check<walberalLM_D3Q27_SRT_COMP,   lbmpyLM_D3Q27_SRT_COMP>  ( blocks, fieldIds[3][0], fieldIds[3][1] );
 
    return EXIT_SUCCESS;
 }
-- 
GitLab