From 473a6ecc8c98c8158fa83dabd931e1ec56c701de Mon Sep 17 00:00:00 2001
From: Christoph Schwarzmeier <christoph.schwarzmeier@fau.de>
Date: Wed, 22 Feb 2023 10:40:48 +0100
Subject: [PATCH] Free surface mass advection benchmark

---
 apps/benchmarks/CMakeLists.txt                |   1 +
 .../FreeSurfaceAdvection/CMakeLists.txt       |  13 +
 .../FreeSurfaceAdvection/DeformationField.cpp | 351 ++++++++++++++++++
 .../FreeSurfaceAdvection/DeformationField.prm |  96 +++++
 .../FreeSurfaceAdvection/SingleVortex.cpp     | 351 ++++++++++++++++++
 .../FreeSurfaceAdvection/SingleVortex.prm     |  96 +++++
 .../FreeSurfaceAdvection}/ZalesakDisk.cpp     | 185 +++++----
 .../FreeSurfaceAdvection}/ZalesakDisk.prm     |  32 +-
 .../functionality/AdvectSweep.h               | 152 ++++++++
 .../functionality/AdvectionDynamicsHandler.h  | 254 +++++++++++++
 .../functionality/GeometricalErrorEvaluator.h | 139 +++++++
 .../FreeSurface/BubblyPoiseuille.cpp          |  19 +-
 .../FreeSurface/BubblyPoiseuille.prm          |   1 +
 apps/showcases/FreeSurface/CMakeLists.txt     |   8 +-
 apps/showcases/FreeSurface/CapillaryWave.cpp  |  15 +-
 .../FreeSurface/DamBreakCylindrical.cpp       |  20 +-
 .../FreeSurface/DamBreakRectangular.cpp       |  19 +-
 apps/showcases/FreeSurface/DropImpact.cpp     |  19 +-
 apps/showcases/FreeSurface/DropImpact.prm     |   1 +
 apps/showcases/FreeSurface/DropWetting.cpp    |  12 +-
 apps/showcases/FreeSurface/GravityWave.cpp    |  17 +-
 .../FreeSurface/GravityWaveCodegen.cpp        |  17 +-
 apps/showcases/FreeSurface/MovingDrop.cpp     |  18 +-
 apps/showcases/FreeSurface/MovingDrop.prm     |  20 +-
 apps/showcases/FreeSurface/RisingBubble.cpp   |  16 +-
 apps/showcases/FreeSurface/TaylorBubble.cpp   |  21 +-
 src/lbm/free_surface/TotalMassComputer.h      |  26 +-
 src/lbm/free_surface/VtkWriter.h              |  35 +-
 .../dynamics/ExcessMassDistributionModel.h    |  68 ++--
 .../dynamics/ExcessMassDistributionSweep.h    |   7 +-
 .../ExcessMassDistributionSweep.impl.h        |  89 +++--
 .../dynamics/SurfaceDynamicsHandler.h         |   6 +-
 .../ExcessMassDistributionParallelTest.cpp    | 128 ++++++-
 .../ExcessMassDistributionParallelTest.ods    | Bin 16650 -> 19371 bytes
 34 files changed, 1994 insertions(+), 258 deletions(-)
 create mode 100644 apps/benchmarks/FreeSurfaceAdvection/CMakeLists.txt
 create mode 100644 apps/benchmarks/FreeSurfaceAdvection/DeformationField.cpp
 create mode 100644 apps/benchmarks/FreeSurfaceAdvection/DeformationField.prm
 create mode 100644 apps/benchmarks/FreeSurfaceAdvection/SingleVortex.cpp
 create mode 100644 apps/benchmarks/FreeSurfaceAdvection/SingleVortex.prm
 rename apps/{showcases/FreeSurface => benchmarks/FreeSurfaceAdvection}/ZalesakDisk.cpp (65%)
 rename apps/{showcases/FreeSurface => benchmarks/FreeSurfaceAdvection}/ZalesakDisk.prm (63%)
 create mode 100644 apps/benchmarks/FreeSurfaceAdvection/functionality/AdvectSweep.h
 create mode 100644 apps/benchmarks/FreeSurfaceAdvection/functionality/AdvectionDynamicsHandler.h
 create mode 100644 apps/benchmarks/FreeSurfaceAdvection/functionality/GeometricalErrorEvaluator.h

diff --git a/apps/benchmarks/CMakeLists.txt b/apps/benchmarks/CMakeLists.txt
index 2adab4750..3f5e6a95a 100644
--- a/apps/benchmarks/CMakeLists.txt
+++ b/apps/benchmarks/CMakeLists.txt
@@ -4,6 +4,7 @@ add_subdirectory( ComplexGeometry )
 add_subdirectory( DEM )
 add_subdirectory( MeshDistance )
 add_subdirectory( CouetteFlow )
+add_subdirectory( FreeSurfaceAdvection )
 add_subdirectory( FluidParticleCoupling )
 add_subdirectory( FluidParticleCouplingWithLoadBalancing )
 add_subdirectory( ForcesOnSphereNearPlaneInShearFlow )
diff --git a/apps/benchmarks/FreeSurfaceAdvection/CMakeLists.txt b/apps/benchmarks/FreeSurfaceAdvection/CMakeLists.txt
new file mode 100644
index 000000000..e14b5fe37
--- /dev/null
+++ b/apps/benchmarks/FreeSurfaceAdvection/CMakeLists.txt
@@ -0,0 +1,13 @@
+waLBerla_link_files_to_builddir( *.prm )
+
+waLBerla_add_executable(NAME    DeformationField
+                        FILES   DeformationField.cpp
+                        DEPENDS blockforest boundary core domain_decomposition field lbm postprocessing timeloop vtk)
+
+waLBerla_add_executable(NAME    SingleVortex
+                        FILES   SingleVortex.cpp
+                        DEPENDS blockforest boundary core domain_decomposition field lbm postprocessing timeloop vtk)
+
+waLBerla_add_executable(NAME    ZalesakDisk
+                        FILES   ZalesakDisk.cpp
+                        DEPENDS blockforest boundary core domain_decomposition field lbm postprocessing timeloop vtk)
\ No newline at end of file
diff --git a/apps/benchmarks/FreeSurfaceAdvection/DeformationField.cpp b/apps/benchmarks/FreeSurfaceAdvection/DeformationField.cpp
new file mode 100644
index 000000000..fcbc4ca54
--- /dev/null
+++ b/apps/benchmarks/FreeSurfaceAdvection/DeformationField.cpp
@@ -0,0 +1,351 @@
+//======================================================================================================================
+//
+//  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 DeformationField.cpp
+//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de>
+//
+// This benchmark simulates the advection of a spherical bubble in a vortex field with very high deformation. The vortex
+// field changes periodically so that the bubble returns to its initial position, where it should take its initial form.
+// The relative geometrical error of the bubble's shape after one period is evaluated. There is no LBM flow simulation
+// performed here. It is a test case for the FSLBM's mass advection only. This benchmark is based on Viktor Haag's
+// master thesis (https://www10.cs.fau.de/publications/theses/2017/Haag_MT_2017.pdf).
+//======================================================================================================================
+
+#include "core/Environment.h"
+
+#include "field/Gather.h"
+
+#include "lbm/PerformanceLogger.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/free_surface/LoadBalancing.h"
+#include "lbm/free_surface/SurfaceMeshWriter.h"
+#include "lbm/free_surface/TotalMassComputer.h"
+#include "lbm/free_surface/VtkWriter.h"
+#include "lbm/free_surface/bubble_model/Geometry.h"
+#include "lbm/free_surface/surface_geometry/SurfaceGeometryHandler.h"
+#include "lbm/free_surface/surface_geometry/Utility.h"
+#include "lbm/lattice_model/D3Q19.h"
+
+#include "functionality/AdvectionDynamicsHandler.h"
+#include "functionality/GeometricalErrorEvaluator.h"
+
+namespace walberla
+{
+namespace free_surface
+{
+namespace DeformationField
+{
+using ScalarField_T = GhostLayerField< real_t, 1 >;
+using VectorField_T = GhostLayerField< Vector3< real_t >, 1 >;
+
+// Lattice model is only created for dummy purposes; no LBM simulation is performed
+using CollisionModel_T      = lbm::collision_model::SRT;
+using LatticeModel_T        = lbm::D3Q19< CollisionModel_T, true >;
+using LatticeModelStencil_T = LatticeModel_T::Stencil;
+using PdfField_T            = lbm::PdfField< LatticeModel_T >;
+using PdfCommunication_T    = blockforest::SimpleCommunication< LatticeModelStencil_T >;
+
+// the geometry computations in SurfaceGeometryHandler require meaningful values in the ghost layers in corner
+// directions (flag field and fill level field); this holds, even if the lattice model uses a D3Q19 stencil
+using CommunicationStencil_T =
+   typename std::conditional< LatticeModel_T::Stencil::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type;
+using Communication_T = blockforest::SimpleCommunication< CommunicationStencil_T >;
+
+using flag_t                        = uint32_t;
+using FlagField_T                   = FlagField< flag_t >;
+using FreeSurfaceBoundaryHandling_T = FreeSurfaceBoundaryHandling< LatticeModel_T, FlagField_T, ScalarField_T >;
+
+// function describing the initialization velocity profile (in global cell coordinates)
+inline Vector3< real_t > velocityProfile(Cell globalCell, real_t timePeriod, uint_t timestep,
+                                         const Vector3< real_t >& domainSize)
+{
+   // add 0.5 to get the cell's center
+   const real_t x = (real_c(globalCell.x()) + real_c(0.5)) / domainSize[0];
+   const real_t y = (real_c(globalCell.y()) + real_c(0.5)) / domainSize[1];
+   const real_t z = (real_c(globalCell.z()) + real_c(0.5)) / domainSize[2];
+
+   const real_t timeTerm = real_c(std::cos(math::pi * real_t(timestep) / timePeriod));
+
+   const real_t sinpix = real_c(std::sin(math::pi * x));
+   const real_t sinpiy = real_c(std::sin(math::pi * y));
+   const real_t sinpiz = real_c(std::sin(math::pi * z));
+
+   const real_t sin2pix = real_c(std::sin(real_c(2) * math::pi * x));
+   const real_t sin2piy = real_c(std::sin(real_c(2) * math::pi * y));
+   const real_t sin2piz = real_c(std::sin(real_c(2) * math::pi * z));
+
+   const real_t velocityX = real_c(2) * sinpix * sinpix * sin2piy * sin2piz * timeTerm;
+   const real_t velocityY = -sin2pix * sinpiy * sinpiy * sin2piz * timeTerm;
+   const real_t velocityZ = -sin2pix * sin2piy * sinpiz * sinpiz * timeTerm;
+
+   return Vector3< real_t >(velocityX, velocityY, velocityZ);
+}
+
+int main(int argc, char** argv)
+{
+   Environment walberlaEnv(argc, argv);
+
+   if (argc < 2) { WALBERLA_ABORT("Please specify a parameter file as input argument.") }
+
+   // print content of parameter file
+   WALBERLA_LOG_INFO_ON_ROOT(*walberlaEnv.config());
+
+   // get block forest parameters from parameter file
+   auto blockForestParameters            = walberlaEnv.config()->getOneBlock("BlockForestParameters");
+   const Vector3< uint_t > cellsPerBlock = blockForestParameters.getParameter< Vector3< uint_t > >("cellsPerBlock");
+   const Vector3< bool > periodicity     = blockForestParameters.getParameter< Vector3< bool > >("periodicity");
+
+   // get domain parameters from parameter file
+   auto domainParameters    = walberlaEnv.config()->getOneBlock("DomainParameters");
+   const uint_t domainWidth = domainParameters.getParameter< uint_t >("domainWidth");
+
+   const real_t bubbleDiameter          = real_c(domainWidth) * real_c(0.3);
+   const Vector3< real_t > bubbleCenter = domainWidth * Vector3< real_t >(real_c(0.35), real_c(0.35), real_c(0.35));
+
+   // define domain size
+   Vector3< uint_t > domainSize;
+   domainSize[0] = domainWidth;
+   domainSize[1] = domainWidth;
+   domainSize[2] = domainWidth;
+
+   // compute number of blocks as defined by domainSize and cellsPerBlock
+   Vector3< uint_t > numBlocks;
+   numBlocks[0] = uint_c(std::ceil(real_c(domainSize[0]) / real_c(cellsPerBlock[0])));
+   numBlocks[1] = uint_c(std::ceil(real_c(domainSize[1]) / real_c(cellsPerBlock[1])));
+   numBlocks[2] = uint_c(std::ceil(real_c(domainSize[2]) / real_c(cellsPerBlock[2])));
+
+   // get number of (MPI) processes
+   const uint_t numProcesses = uint_c(MPIManager::instance()->numProcesses());
+   WALBERLA_CHECK_LESS_EQUAL(numProcesses, numBlocks[0] * numBlocks[1] * numBlocks[2],
+                             "The number of MPI processes is greater than the number of blocks as defined by "
+                             "\"domainSize/cellsPerBlock\". This would result in unused MPI processes. Either decrease "
+                             "the number of MPI processes or increase \"cellsPerBlock\".")
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(numProcesses);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(cellsPerBlock);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(domainSize);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(numBlocks);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(domainWidth);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(periodicity);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(bubbleDiameter);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(bubbleCenter);
+
+   // get physics parameters from parameter file
+   auto physicsParameters                  = walberlaEnv.config()->getOneBlock("PhysicsParameters");
+   const uint_t timesteps                  = physicsParameters.getParameter< uint_t >("timesteps");
+   const uint_t timestepsToInitialPosition = physicsParameters.getParameter< uint_t >("timestepsToInitialPosition");
+
+   // compute CFL number
+   const real_t dx_SI = real_c(1) / real_c(domainWidth);
+   const real_t dt_SI = real_c(3) / real_c(timestepsToInitialPosition);
+   const real_t CFL   = dt_SI / dx_SI; // with velocity_SI = 1
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(CFL);
+
+   // dummy collision model (LBM not simulated in this benchmark)
+   const CollisionModel_T collisionModel = CollisionModel_T(real_c(1));
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(timestepsToInitialPosition);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(timesteps);
+
+   // read model parameters from parameter file
+   const auto modelParameters               = walberlaEnv.config()->getOneBlock("ModelParameters");
+   const std::string pdfReconstructionModel = modelParameters.getParameter< std::string >("pdfReconstructionModel");
+   const std::string excessMassDistributionModel =
+      modelParameters.getParameter< std::string >("excessMassDistributionModel");
+   const std::string curvatureModel          = modelParameters.getParameter< std::string >("curvatureModel");
+   const bool useSimpleMassExchange          = modelParameters.getParameter< bool >("useSimpleMassExchange");
+   const real_t cellConversionThreshold      = modelParameters.getParameter< real_t >("cellConversionThreshold");
+   const real_t cellConversionForceThreshold = modelParameters.getParameter< real_t >("cellConversionForceThreshold");
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(pdfReconstructionModel);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(excessMassDistributionModel);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(curvatureModel);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(useSimpleMassExchange);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(cellConversionThreshold);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(cellConversionForceThreshold);
+
+   // read evaluation parameters from parameter file
+   const auto evaluationParameters      = walberlaEnv.config()->getOneBlock("EvaluationParameters");
+   const uint_t performanceLogFrequency = evaluationParameters.getParameter< uint_t >("performanceLogFrequency");
+   const uint_t evaluationFrequency     = evaluationParameters.getParameter< uint_t >("evaluationFrequency");
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(performanceLogFrequency);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(evaluationFrequency);
+
+   // create non-uniform block forest (non-uniformity required for load balancing)
+   const std::shared_ptr< StructuredBlockForest > blockForest =
+      createNonUniformBlockForest(domainSize, cellsPerBlock, numBlocks, periodicity);
+
+   // create lattice model
+   const LatticeModel_T latticeModel = LatticeModel_T(collisionModel);
+
+   // add pdf field
+   const BlockDataID pdfFieldID = lbm::addPdfFieldToStorage(blockForest, "PDF field", latticeModel, field::fzyx);
+
+   // add fill level field (initialized with 1, i.e., liquid everywhere)
+   const BlockDataID fillFieldID =
+      field::addToStorage< ScalarField_T >(blockForest, "Fill level field", real_c(1.0), field::fzyx, uint_c(2));
+
+   // add boundary handling
+   const std::shared_ptr< FreeSurfaceBoundaryHandling_T > freeSurfaceBoundaryHandling =
+      std::make_shared< FreeSurfaceBoundaryHandling_T >(blockForest, pdfFieldID, fillFieldID);
+   const BlockDataID flagFieldID                                      = freeSurfaceBoundaryHandling->getFlagFieldID();
+   const typename FreeSurfaceBoundaryHandling_T::FlagInfo_T& flagInfo = freeSurfaceBoundaryHandling->getFlagInfo();
+
+   // initialize the velocity profile
+   for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt)
+   {
+      PdfField_T* const pdfField   = blockIt->getData< PdfField_T >(pdfFieldID);
+      FlagField_T* const flagField = blockIt->getData< FlagField_T >(flagFieldID);
+
+      WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, {
+         //  cell in block-local coordinates
+         const Cell localCell = pdfFieldIt.cell();
+
+         // get cell in global coordinates
+         Cell globalCell = pdfFieldIt.cell();
+         blockForest->transformBlockLocalToGlobalCell(globalCell, *blockIt, localCell);
+
+         // set velocity profile (CFL_SI = CFL_LBM = velocity_LBM * dt_LBM / dx_LBM = velocity_LBM)
+         const Vector3< real_t > initialVelocity =
+            CFL * velocityProfile(globalCell, real_c(timestepsToInitialPosition), uint_c(0), domainSize);
+         pdfField->setDensityAndVelocity(localCell, initialVelocity, real_c(1));
+      }) // WALBERLA_FOR_ALL_CELLS
+   }
+
+   // create the spherical bubble
+   const geometry::Sphere sphereBubble(real_c(domainWidth) * Vector3< real_t >(real_c(0.5), real_c(0.75), real_c(0.25)),
+                                       real_c(domainWidth) * real_c(0.15));
+   bubble_model::addBodyToFillLevelField< geometry::Sphere >(*blockForest, fillFieldID, sphereBubble, true);
+
+   // initialize domain boundary conditions from config file
+   const auto boundaryParameters = walberlaEnv.config()->getOneBlock("BoundaryParameters");
+   freeSurfaceBoundaryHandling->initFromConfig(boundaryParameters);
+
+   // IMPORTANT REMARK: this must be called only after every solid flag has been set; otherwise, the boundary handling
+   // might not detect solid flags correctly
+   freeSurfaceBoundaryHandling->initFlagsFromFillLevel();
+
+   // communication after initialization
+   Communication_T communication(blockForest, flagFieldID, fillFieldID);
+   communication();
+
+   PdfCommunication_T pdfCommunication(blockForest, pdfFieldID);
+   pdfCommunication();
+
+   const ConstBlockDataID initialFillFieldID =
+      field::addCloneToStorage< ScalarField_T >(blockForest, fillFieldID, "Initial fill level field");
+
+   // add bubble model
+   const std::shared_ptr< bubble_model::BubbleModelBase > bubbleModel =
+      std::make_shared< bubble_model::BubbleModelConstantPressure >(real_c(1));
+
+   // create timeloop
+   SweepTimeloop timeloop(blockForest, timesteps);
+
+   // add surface geometry handler
+   const SurfaceGeometryHandler< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T > geometryHandler(
+      blockForest, freeSurfaceBoundaryHandling, fillFieldID, curvatureModel, false, false, real_c(0));
+
+   geometryHandler.addSweeps(timeloop);
+
+   // get fields created by surface geometry handler
+   const ConstBlockDataID normalFieldID = geometryHandler.getConstNormalFieldID();
+
+   // add boundary handling for standard boundaries and free surface boundaries
+   const AdvectionDynamicsHandler< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T > dynamicsHandler(
+      blockForest, pdfFieldID, flagFieldID, fillFieldID, normalFieldID, freeSurfaceBoundaryHandling, bubbleModel,
+      pdfReconstructionModel, excessMassDistributionModel, useSimpleMassExchange, cellConversionThreshold,
+      cellConversionForceThreshold);
+
+   dynamicsHandler.addSweeps(timeloop);
+
+   // add evaluator for geometrical
+   const std::shared_ptr< real_t > geometricalError = std::make_shared< real_t >(real_c(0));
+   const GeometricalErrorEvaluator< FreeSurfaceBoundaryHandling_T, FlagField_T, ScalarField_T >
+      geometricalErrorEvaluator(blockForest, freeSurfaceBoundaryHandling, initialFillFieldID, fillFieldID,
+                                evaluationFrequency, geometricalError);
+   timeloop.addFuncAfterTimeStep(geometricalErrorEvaluator, "Evaluator: geometrical error");
+
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
+   // add VTK output
+   addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
+      blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, BlockDataID(),
+      geometryHandler.getCurvatureFieldID(), geometryHandler.getNormalFieldID(),
+      geometryHandler.getObstNormalFieldID());
+
+   // add triangle mesh output of free surface
+   SurfaceMeshWriter< ScalarField_T, FlagField_T > surfaceMeshWriter(
+      blockForest, fillFieldID, flagFieldID, flagIDs::liquidInterfaceGasFlagIDs, real_c(0), walberlaEnv.config());
+   surfaceMeshWriter(); // write initial mesh
+   timeloop.addFuncAfterTimeStep(surfaceMeshWriter, "Writer: surface mesh");
+
+   // add logging for computational performance
+   const lbm::PerformanceLogger< FlagField_T > performanceLogger(
+      blockForest, flagFieldID, flagIDs::liquidInterfaceFlagIDs, performanceLogFrequency);
+   timeloop.addFuncAfterTimeStep(performanceLogger, "Evaluator: performance logging");
+
+   WcTimingPool timingPool;
+
+   for (uint_t t = uint_c(0); t != timesteps; ++t)
+   {
+      timeloop.singleStep(timingPool, true);
+
+      if (t % evaluationFrequency == uint_c(0))
+      {
+         WALBERLA_LOG_DEVEL_ON_ROOT("time step = " << t << "\n\t\ttotal mass = " << *totalMass << "\n\t\texcess mass = "
+                                                   << *excessMass << "\n\t\tgeometrical error = " << *geometricalError);
+      }
+
+      // set the constant velocity profile
+      for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt)
+      {
+         PdfField_T* const pdfField   = blockIt->getData< PdfField_T >(pdfFieldID);
+         FlagField_T* const flagField = blockIt->getData< FlagField_T >(flagFieldID);
+
+         WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, {
+            const Cell localCell = pdfFieldIt.cell();
+
+            // get cell in global coordinates
+            Cell globalCell = pdfFieldIt.cell();
+            blockForest->transformBlockLocalToGlobalCell(globalCell, *blockIt, localCell);
+
+            // set velocity profile (CFL_SI = CFL_LBM = velocity_LBM * dt_LBM / dx_LBM = velocity_LBM)
+            const Vector3< real_t > velocity =
+               CFL * velocityProfile(globalCell, real_c(timestepsToInitialPosition), t, domainSize);
+            pdfField->setDensityAndVelocity(localCell, velocity, real_c(1));
+         }) // WALBERLA_FOR_ALL_CELLS
+      }
+
+      pdfCommunication();
+
+      if (t % performanceLogFrequency == uint_c(0) && t > uint_c(0)) { timingPool.logResultOnRoot(); }
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace DeformationField
+} // namespace free_surface
+} // namespace walberla
+
+int main(int argc, char** argv) { return walberla::free_surface::DeformationField::main(argc, argv); }
\ No newline at end of file
diff --git a/apps/benchmarks/FreeSurfaceAdvection/DeformationField.prm b/apps/benchmarks/FreeSurfaceAdvection/DeformationField.prm
new file mode 100644
index 000000000..359fd4a98
--- /dev/null
+++ b/apps/benchmarks/FreeSurfaceAdvection/DeformationField.prm
@@ -0,0 +1,96 @@
+BlockForestParameters
+{
+   cellsPerBlock                 < 25, 25, 25 >;
+   periodicity                   < 1, 1, 1 >;
+}
+
+DomainParameters
+{
+   domainWidth       50;
+}
+
+PhysicsParameters
+{
+   timestepsToInitialPosition 3000;
+   timesteps                  3001;
+
+}
+
+ModelParameters
+{
+   pdfReconstructionModel        OnlyMissing;
+   excessMassDistributionModel   EvenlyAllInterface;
+   curvatureModel                FiniteDifferenceMethod;
+   useSimpleMassExchange         false;
+   cellConversionThreshold       1e-2;
+   cellConversionForceThreshold  1e-1;
+}
+
+EvaluationParameters
+{
+   evaluationFrequency 300;
+   performanceLogFrequency 10000;
+}
+
+BoundaryParameters
+{
+   // X
+   //Border { direction W;  walldistance -1; FreeSlip{} }
+   //Border { direction E;  walldistance -1; FreeSlip{} }
+
+   // Y
+   //Border { direction N;  walldistance -1; FreeSlip{} }
+   //Border { direction S;  walldistance -1; FreeSlip{} }
+
+   // Z
+   //Border { direction T;  walldistance -1; FreeSlip{} }
+   //Border { direction B;  walldistance -1; FreeSlip{} }
+}
+
+MeshOutputParameters
+{
+   writeFrequency 300;
+   baseFolder     mesh-out;
+}
+
+VTK
+{
+   fluid_field
+   {
+      writeFrequency       300;
+      ghostLayers          0;
+      baseFolder           vtk-out;
+      samplingResolution   1;
+
+      writers
+      {
+         fill_level;
+         mapped_flag;
+         velocity;
+         density;
+         //curvature;
+         //normal;
+         //obstacle_normal;
+         //pdf;
+         //flag;
+      }
+
+      inclusion_filters
+      {
+         //liquidInterfaceFilter; // only include liquid and interface cells in VTK output
+      }
+
+      before_functions
+      {
+         //ghost_layer_synchronization;   // only needed if writing the ghost layer
+         gas_cell_zero_setter;            // sets velocity=0 and density=1 all gas cells before writing VTK output
+      }
+   }
+
+   domain_decomposition
+   {
+      writeFrequency             0;
+      baseFolder                 vtk-out;
+      outputDomainDecomposition  true;
+   }
+}
\ No newline at end of file
diff --git a/apps/benchmarks/FreeSurfaceAdvection/SingleVortex.cpp b/apps/benchmarks/FreeSurfaceAdvection/SingleVortex.cpp
new file mode 100644
index 000000000..d3c8e01ed
--- /dev/null
+++ b/apps/benchmarks/FreeSurfaceAdvection/SingleVortex.cpp
@@ -0,0 +1,351 @@
+//======================================================================================================================
+//
+//  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 SingleVortex.cpp
+//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de>
+//
+// This benchmark simulates the advection of a spherical bubble in a vortex field.
+// The vortex field changes periodically so that the bubble returns to its initial position, where it should take its
+// initial form. The relative geometrical error of the bubble's shape after one period is evaluated. There is no LBM
+// flow simulation performed here, it is a test case for the FSLBM's mass advection. This benchmark is based on the
+// two-dimensional test case from Rider and Kothe (doi: 10.1006/jcph.1998.5906). The extension to three-dimensional
+// space is based on the Viktor Haag's master thesis
+// (https://www10.cs.fau.de/publications/theses/2017/Haag_MT_2017.pdf).
+//======================================================================================================================
+
+#include "core/Environment.h"
+
+#include "field/Gather.h"
+
+#include "lbm/PerformanceLogger.h"
+#include "lbm/field/AddToStorage.h"
+#include "lbm/free_surface/LoadBalancing.h"
+#include "lbm/free_surface/SurfaceMeshWriter.h"
+#include "lbm/free_surface/TotalMassComputer.h"
+#include "lbm/free_surface/VtkWriter.h"
+#include "lbm/free_surface/bubble_model/Geometry.h"
+#include "lbm/free_surface/surface_geometry/SurfaceGeometryHandler.h"
+#include "lbm/free_surface/surface_geometry/Utility.h"
+#include "lbm/lattice_model/D3Q19.h"
+
+#include "functionality/AdvectionDynamicsHandler.h"
+#include "functionality/GeometricalErrorEvaluator.h"
+
+namespace walberla
+{
+namespace free_surface
+{
+namespace SingleVortex
+{
+using ScalarField_T = GhostLayerField< real_t, 1 >;
+using VectorField_T = GhostLayerField< Vector3< real_t >, 1 >;
+
+// Lattice model is only created for dummy purposes; no LBM simulation is performed
+using CollisionModel_T      = lbm::collision_model::SRT;
+using LatticeModel_T        = lbm::D3Q19< CollisionModel_T, true >;
+using LatticeModelStencil_T = LatticeModel_T::Stencil;
+using PdfField_T            = lbm::PdfField< LatticeModel_T >;
+using PdfCommunication_T    = blockforest::SimpleCommunication< LatticeModelStencil_T >;
+
+// the geometry computations in SurfaceGeometryHandler require meaningful values in the ghost layers in corner
+// directions (flag field and fill level field); this holds, even if the lattice model uses a D3Q19 stencil
+using CommunicationStencil_T =
+   typename std::conditional< LatticeModel_T::Stencil::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type;
+using Communication_T = blockforest::SimpleCommunication< CommunicationStencil_T >;
+
+using flag_t                        = uint32_t;
+using FlagField_T                   = FlagField< flag_t >;
+using FreeSurfaceBoundaryHandling_T = FreeSurfaceBoundaryHandling< LatticeModel_T, FlagField_T, ScalarField_T >;
+
+// function describing the initialization velocity profile (in global cell coordinates)
+inline Vector3< real_t > velocityProfile(Cell globalCell, real_t timePeriod, uint_t timestep,
+                                         const Vector3< real_t >& domainSize)
+{
+   // add 0.5 to get the cell's center
+   const real_t x = (real_c(globalCell.x()) + real_c(0.5)) / domainSize[0];
+   const real_t y = (real_c(globalCell.y()) + real_c(0.5)) / domainSize[1];
+
+   const real_t xToDomainCenter = x - real_c(0.5);
+   const real_t yToDomainCenter = y - real_c(0.5);
+   const real_t r     = real_c(std::sqrt(xToDomainCenter * xToDomainCenter + yToDomainCenter * yToDomainCenter));
+   const real_t rTerm = (real_c(1) - real_c(2) * r) * (real_c(1) - real_c(2) * r);
+
+   const real_t timeTerm = real_c(std::cos(math::pi * real_t(timestep) / timePeriod));
+
+   const real_t velocityX = real_c(std::sin(real_c(2) * math::pi * y)) * real_c(std::sin(math::pi * x)) *
+                            real_c(std::sin(math::pi * x)) * timeTerm;
+   const real_t velocityY = -real_c(std::sin(real_c(2) * math::pi * x)) * real_c(std::sin(math::pi * y)) *
+                            real_c(std::sin(math::pi * y)) * timeTerm;
+   const real_t velocityZ = rTerm * timeTerm;
+
+   return Vector3< real_t >(velocityX, velocityY, velocityZ);
+}
+
+int main(int argc, char** argv)
+{
+   Environment walberlaEnv(argc, argv);
+
+   if (argc < 2) { WALBERLA_ABORT("Please specify a parameter file as input argument.") }
+
+   // print content of parameter file
+   WALBERLA_LOG_INFO_ON_ROOT(*walberlaEnv.config());
+
+   // get block forest parameters from parameter file
+   auto blockForestParameters            = walberlaEnv.config()->getOneBlock("BlockForestParameters");
+   const Vector3< uint_t > cellsPerBlock = blockForestParameters.getParameter< Vector3< uint_t > >("cellsPerBlock");
+   const Vector3< bool > periodicity     = blockForestParameters.getParameter< Vector3< bool > >("periodicity");
+
+   // get domain parameters from parameter file
+   auto domainParameters    = walberlaEnv.config()->getOneBlock("DomainParameters");
+   const uint_t domainWidth = domainParameters.getParameter< uint_t >("domainWidth");
+
+   const real_t bubbleDiameter          = real_c(domainWidth) * real_c(0.075);
+   const Vector3< real_t > bubbleCenter = domainWidth * Vector3< real_t >(real_c(0.5), real_c(0.75), real_c(0.25));
+
+   // define domain size
+   Vector3< uint_t > domainSize;
+   domainSize[0] = domainWidth;
+   domainSize[1] = domainWidth;
+   domainSize[2] = domainWidth;
+
+   // compute number of blocks as defined by domainSize and cellsPerBlock
+   Vector3< uint_t > numBlocks;
+   numBlocks[0] = uint_c(std::ceil(real_c(domainSize[0]) / real_c(cellsPerBlock[0])));
+   numBlocks[1] = uint_c(std::ceil(real_c(domainSize[1]) / real_c(cellsPerBlock[1])));
+   numBlocks[2] = uint_c(std::ceil(real_c(domainSize[2]) / real_c(cellsPerBlock[2])));
+
+   // get number of (MPI) processes
+   const uint_t numProcesses = uint_c(MPIManager::instance()->numProcesses());
+   WALBERLA_CHECK_LESS_EQUAL(numProcesses, numBlocks[0] * numBlocks[1] * numBlocks[2],
+                             "The number of MPI processes is greater than the number of blocks as defined by "
+                             "\"domainSize/cellsPerBlock\". This would result in unused MPI processes. Either decrease "
+                             "the number of MPI processes or increase \"cellsPerBlock\".")
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(numProcesses);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(cellsPerBlock);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(domainSize);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(numBlocks);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(domainWidth);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(periodicity);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(bubbleDiameter);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(bubbleCenter);
+
+   // get physics parameters from parameter file
+   auto physicsParameters                  = walberlaEnv.config()->getOneBlock("PhysicsParameters");
+   const uint_t timesteps                  = physicsParameters.getParameter< uint_t >("timesteps");
+   const uint_t timestepsToInitialPosition = physicsParameters.getParameter< uint_t >("timestepsToInitialPosition");
+
+   // compute CFL number
+   const real_t dx_SI = real_c(1) / real_c(domainWidth);
+   const real_t dt_SI = real_c(3) / real_c(timestepsToInitialPosition);
+   const real_t CFL   = dt_SI / dx_SI; // with velocity_SI = 1
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(CFL);
+
+   // dummy collision model (LBM not simulated in this benchmark)
+   const CollisionModel_T collisionModel = CollisionModel_T(real_c(1));
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(timestepsToInitialPosition);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(timesteps);
+
+   // read model parameters from parameter file
+   const auto modelParameters               = walberlaEnv.config()->getOneBlock("ModelParameters");
+   const std::string pdfReconstructionModel = modelParameters.getParameter< std::string >("pdfReconstructionModel");
+   const std::string excessMassDistributionModel =
+      modelParameters.getParameter< std::string >("excessMassDistributionModel");
+   const std::string curvatureModel          = modelParameters.getParameter< std::string >("curvatureModel");
+   const bool useSimpleMassExchange          = modelParameters.getParameter< bool >("useSimpleMassExchange");
+   const real_t cellConversionThreshold      = modelParameters.getParameter< real_t >("cellConversionThreshold");
+   const real_t cellConversionForceThreshold = modelParameters.getParameter< real_t >("cellConversionForceThreshold");
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(pdfReconstructionModel);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(excessMassDistributionModel);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(curvatureModel);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(useSimpleMassExchange);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(cellConversionThreshold);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(cellConversionForceThreshold);
+
+   // read evaluation parameters from parameter file
+   const auto evaluationParameters      = walberlaEnv.config()->getOneBlock("EvaluationParameters");
+   const uint_t performanceLogFrequency = evaluationParameters.getParameter< uint_t >("performanceLogFrequency");
+   const uint_t evaluationFrequency     = evaluationParameters.getParameter< uint_t >("evaluationFrequency");
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(performanceLogFrequency);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(evaluationFrequency);
+
+   // create non-uniform block forest (non-uniformity required for load balancing)
+   const std::shared_ptr< StructuredBlockForest > blockForest =
+      createNonUniformBlockForest(domainSize, cellsPerBlock, numBlocks, periodicity);
+
+   // create lattice model
+   const LatticeModel_T latticeModel = LatticeModel_T(collisionModel);
+
+   // add pdf field
+   const BlockDataID pdfFieldID = lbm::addPdfFieldToStorage(blockForest, "PDF field", latticeModel, field::fzyx);
+
+   // add fill level field (initialized with 1, i.e., liquid everywhere)
+   const BlockDataID fillFieldID =
+      field::addToStorage< ScalarField_T >(blockForest, "Fill level field", real_c(1.0), field::fzyx, uint_c(2));
+
+   // add boundary handling
+   const std::shared_ptr< FreeSurfaceBoundaryHandling_T > freeSurfaceBoundaryHandling =
+      std::make_shared< FreeSurfaceBoundaryHandling_T >(blockForest, pdfFieldID, fillFieldID);
+   const BlockDataID flagFieldID                                      = freeSurfaceBoundaryHandling->getFlagFieldID();
+   const typename FreeSurfaceBoundaryHandling_T::FlagInfo_T& flagInfo = freeSurfaceBoundaryHandling->getFlagInfo();
+
+   // initialize the velocity profile
+   for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt)
+   {
+      PdfField_T* const pdfField   = blockIt->getData< PdfField_T >(pdfFieldID);
+      FlagField_T* const flagField = blockIt->getData< FlagField_T >(flagFieldID);
+
+      WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, {
+         //  cell in block-local coordinates
+         const Cell localCell = pdfFieldIt.cell();
+
+         // get cell in global coordinates
+         Cell globalCell = pdfFieldIt.cell();
+         blockForest->transformBlockLocalToGlobalCell(globalCell, *blockIt, localCell);
+
+         // set velocity profile (CFL_SI = CFL_LBM = velocity_LBM * dt_LBM / dx_LBM = velocity_LBM)
+         const Vector3< real_t > initialVelocity =
+            CFL * velocityProfile(globalCell, real_c(timestepsToInitialPosition), uint_c(0), domainSize);
+         pdfField->setDensityAndVelocity(localCell, initialVelocity, real_c(1));
+      }) // WALBERLA_FOR_ALL_CELLS
+   }
+
+   // create the spherical bubble
+   const geometry::Sphere sphereBubble(real_c(domainWidth) * Vector3< real_t >(real_c(0.5), real_c(0.75), real_c(0.25)),
+                                       real_c(domainWidth) * real_c(0.15));
+   bubble_model::addBodyToFillLevelField< geometry::Sphere >(*blockForest, fillFieldID, sphereBubble, true);
+
+   // initialize domain boundary conditions from config file
+   const auto boundaryParameters = walberlaEnv.config()->getOneBlock("BoundaryParameters");
+   freeSurfaceBoundaryHandling->initFromConfig(boundaryParameters);
+
+   // IMPORTANT REMARK: this must be called only after every solid flag has been set; otherwise, the boundary handling
+   // might not detect solid flags correctly
+   freeSurfaceBoundaryHandling->initFlagsFromFillLevel();
+
+   // communication after initialization
+   Communication_T communication(blockForest, flagFieldID, fillFieldID);
+   communication();
+
+   PdfCommunication_T pdfCommunication(blockForest, pdfFieldID);
+   pdfCommunication();
+
+   const ConstBlockDataID initialFillFieldID =
+      field::addCloneToStorage< ScalarField_T >(blockForest, fillFieldID, "Initial fill level field");
+
+   // add bubble model
+   const std::shared_ptr< bubble_model::BubbleModelBase > bubbleModel =
+      std::make_shared< bubble_model::BubbleModelConstantPressure >(real_c(1));
+
+   // create timeloop
+   SweepTimeloop timeloop(blockForest, timesteps);
+
+   // add surface geometry handler
+   const SurfaceGeometryHandler< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T > geometryHandler(
+      blockForest, freeSurfaceBoundaryHandling, fillFieldID, curvatureModel, false, false, real_c(0));
+
+   geometryHandler.addSweeps(timeloop);
+
+   // get fields created by surface geometry handler
+   const ConstBlockDataID normalFieldID = geometryHandler.getConstNormalFieldID();
+
+   // add boundary handling for standard boundaries and free surface boundaries
+   const AdvectionDynamicsHandler< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T > dynamicsHandler(
+      blockForest, pdfFieldID, flagFieldID, fillFieldID, normalFieldID, freeSurfaceBoundaryHandling, bubbleModel,
+      pdfReconstructionModel, excessMassDistributionModel, useSimpleMassExchange, cellConversionThreshold,
+      cellConversionForceThreshold);
+
+   dynamicsHandler.addSweeps(timeloop);
+
+   // add evaluator for geometrical
+   const std::shared_ptr< real_t > geometricalError = std::make_shared< real_t >(real_c(0));
+   const GeometricalErrorEvaluator< FreeSurfaceBoundaryHandling_T, FlagField_T, ScalarField_T >
+      geometricalErrorEvaluator(blockForest, freeSurfaceBoundaryHandling, initialFillFieldID, fillFieldID,
+                                evaluationFrequency, geometricalError);
+   timeloop.addFuncAfterTimeStep(geometricalErrorEvaluator, "Evaluator: geometrical error");
+
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
+   // add VTK output
+   addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
+      blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, BlockDataID(),
+      geometryHandler.getCurvatureFieldID(), geometryHandler.getNormalFieldID(),
+      geometryHandler.getObstNormalFieldID());
+
+   // add triangle mesh output of free surface
+   SurfaceMeshWriter< ScalarField_T, FlagField_T > surfaceMeshWriter(
+      blockForest, fillFieldID, flagFieldID, flagIDs::liquidInterfaceGasFlagIDs, real_c(0), walberlaEnv.config());
+   surfaceMeshWriter(); // write initial mesh
+   timeloop.addFuncAfterTimeStep(surfaceMeshWriter, "Writer: surface mesh");
+
+   // add logging for computational performance
+   const lbm::PerformanceLogger< FlagField_T > performanceLogger(
+      blockForest, flagFieldID, flagIDs::liquidInterfaceFlagIDs, performanceLogFrequency);
+   timeloop.addFuncAfterTimeStep(performanceLogger, "Evaluator: performance logging");
+
+   WcTimingPool timingPool;
+
+   for (uint_t t = uint_c(0); t != timesteps; ++t)
+   {
+      timeloop.singleStep(timingPool, true);
+
+      if (t % evaluationFrequency == uint_c(0))
+      {
+         WALBERLA_LOG_DEVEL_ON_ROOT("time step = " << t << "\n\t\ttotal mass = " << *totalMass << "\n\t\texcess mass = "
+                                                   << *excessMass << "\n\t\tgeometrical error = " << *geometricalError);
+      }
+
+      // set the constant velocity profile
+      for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt)
+      {
+         PdfField_T* const pdfField   = blockIt->getData< PdfField_T >(pdfFieldID);
+         FlagField_T* const flagField = blockIt->getData< FlagField_T >(flagFieldID);
+
+         WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, {
+            const Cell localCell = pdfFieldIt.cell();
+
+            // get cell in global coordinates
+            Cell globalCell = pdfFieldIt.cell();
+            blockForest->transformBlockLocalToGlobalCell(globalCell, *blockIt, localCell);
+
+            // set velocity profile (CFL_SI = CFL_LBM = velocity_LBM * dt_LBM / dx_LBM = velocity_LBM)
+            const Vector3< real_t > velocity =
+               CFL * velocityProfile(globalCell, real_c(timestepsToInitialPosition), t, domainSize);
+            pdfField->setDensityAndVelocity(localCell, velocity, real_c(1));
+         }) // WALBERLA_FOR_ALL_CELLS
+      }
+
+      pdfCommunication();
+
+      if (t % performanceLogFrequency == uint_c(0) && t > uint_c(0)) { timingPool.logResultOnRoot(); }
+   }
+
+   return EXIT_SUCCESS;
+}
+
+} // namespace SingleVortex
+} // namespace free_surface
+} // namespace walberla
+
+int main(int argc, char** argv) { return walberla::free_surface::SingleVortex::main(argc, argv); }
\ No newline at end of file
diff --git a/apps/benchmarks/FreeSurfaceAdvection/SingleVortex.prm b/apps/benchmarks/FreeSurfaceAdvection/SingleVortex.prm
new file mode 100644
index 000000000..359fd4a98
--- /dev/null
+++ b/apps/benchmarks/FreeSurfaceAdvection/SingleVortex.prm
@@ -0,0 +1,96 @@
+BlockForestParameters
+{
+   cellsPerBlock                 < 25, 25, 25 >;
+   periodicity                   < 1, 1, 1 >;
+}
+
+DomainParameters
+{
+   domainWidth       50;
+}
+
+PhysicsParameters
+{
+   timestepsToInitialPosition 3000;
+   timesteps                  3001;
+
+}
+
+ModelParameters
+{
+   pdfReconstructionModel        OnlyMissing;
+   excessMassDistributionModel   EvenlyAllInterface;
+   curvatureModel                FiniteDifferenceMethod;
+   useSimpleMassExchange         false;
+   cellConversionThreshold       1e-2;
+   cellConversionForceThreshold  1e-1;
+}
+
+EvaluationParameters
+{
+   evaluationFrequency 300;
+   performanceLogFrequency 10000;
+}
+
+BoundaryParameters
+{
+   // X
+   //Border { direction W;  walldistance -1; FreeSlip{} }
+   //Border { direction E;  walldistance -1; FreeSlip{} }
+
+   // Y
+   //Border { direction N;  walldistance -1; FreeSlip{} }
+   //Border { direction S;  walldistance -1; FreeSlip{} }
+
+   // Z
+   //Border { direction T;  walldistance -1; FreeSlip{} }
+   //Border { direction B;  walldistance -1; FreeSlip{} }
+}
+
+MeshOutputParameters
+{
+   writeFrequency 300;
+   baseFolder     mesh-out;
+}
+
+VTK
+{
+   fluid_field
+   {
+      writeFrequency       300;
+      ghostLayers          0;
+      baseFolder           vtk-out;
+      samplingResolution   1;
+
+      writers
+      {
+         fill_level;
+         mapped_flag;
+         velocity;
+         density;
+         //curvature;
+         //normal;
+         //obstacle_normal;
+         //pdf;
+         //flag;
+      }
+
+      inclusion_filters
+      {
+         //liquidInterfaceFilter; // only include liquid and interface cells in VTK output
+      }
+
+      before_functions
+      {
+         //ghost_layer_synchronization;   // only needed if writing the ghost layer
+         gas_cell_zero_setter;            // sets velocity=0 and density=1 all gas cells before writing VTK output
+      }
+   }
+
+   domain_decomposition
+   {
+      writeFrequency             0;
+      baseFolder                 vtk-out;
+      outputDomainDecomposition  true;
+   }
+}
\ No newline at end of file
diff --git a/apps/showcases/FreeSurface/ZalesakDisk.cpp b/apps/benchmarks/FreeSurfaceAdvection/ZalesakDisk.cpp
similarity index 65%
rename from apps/showcases/FreeSurface/ZalesakDisk.cpp
rename to apps/benchmarks/FreeSurfaceAdvection/ZalesakDisk.cpp
index edbaab294..f2177d559 100644
--- a/apps/showcases/FreeSurface/ZalesakDisk.cpp
+++ b/apps/benchmarks/FreeSurfaceAdvection/ZalesakDisk.cpp
@@ -16,12 +16,14 @@
 //! \file ZalesakDisk.cpp
 //! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de>
 //
-// This showcase simulates a slotted disk of gas in a constant rotating velocity field. This benchmark is commonly
-// referred to as Zalesak's rotating disk (see doi: 10.1016/0021-9991(79)90051-2).
+// This benchmark simulates the advection of a slotted disk of gas in a constant rotating velocity field. The disk
+// returns to its initial position, where it should take its initial form. The relative geometrical error of the
+// bubble's shape after one rotation is evaluated. There is no LBM flow simulation performed here, it is a test case for
+// the FSLBM's mass advection. This benchmark is commonly referred to as Zalesak's rotating disk (see
+// doi: 10.1016/0021-9991(79)90051-2). The setup chosen here is identical to the one used by Janssen (see
+// doi: 10.1016/j.camwa.2009.08.064).
 //======================================================================================================================
 
-#include "blockforest/Initialization.h"
-
 #include "core/Environment.h"
 
 #include "field/Gather.h"
@@ -33,11 +35,13 @@
 #include "lbm/free_surface/TotalMassComputer.h"
 #include "lbm/free_surface/VtkWriter.h"
 #include "lbm/free_surface/bubble_model/Geometry.h"
-#include "lbm/free_surface/dynamics/SurfaceDynamicsHandler.h"
 #include "lbm/free_surface/surface_geometry/SurfaceGeometryHandler.h"
 #include "lbm/free_surface/surface_geometry/Utility.h"
 #include "lbm/lattice_model/D2Q9.h"
 
+#include "functionality/AdvectionDynamicsHandler.h"
+#include "functionality/GeometricalErrorEvaluator.h"
+
 namespace walberla
 {
 namespace free_surface
@@ -47,9 +51,9 @@ namespace ZalesakDisk
 using ScalarField_T = GhostLayerField< real_t, 1 >;
 using VectorField_T = GhostLayerField< Vector3< real_t >, 1 >;
 
+// Lattice model is only created for dummy purposes; no LBM simulation is performed
 using CollisionModel_T      = lbm::collision_model::SRT;
-using ForceModel_T          = lbm::force_model::GuoField< VectorField_T >;
-using LatticeModel_T        = lbm::D2Q9< CollisionModel_T, true, ForceModel_T, 2 >;
+using LatticeModel_T        = lbm::D2Q9< CollisionModel_T, true >;
 using LatticeModelStencil_T = LatticeModel_T::Stencil;
 using PdfField_T            = lbm::PdfField< LatticeModel_T >;
 using PdfCommunication_T    = blockforest::SimpleCommunication< LatticeModelStencil_T >;
@@ -65,7 +69,7 @@ using FlagField_T                   = FlagField< flag_t >;
 using FreeSurfaceBoundaryHandling_T = FreeSurfaceBoundaryHandling< LatticeModel_T, FlagField_T, ScalarField_T >;
 
 // function describing the initialization velocity profile (in global cell coordinates)
-inline Vector3< real_t > velocityProfile(real_t angularVelocity, Cell globalCell, Vector3< real_t > domainCenter)
+inline Vector3< real_t > velocityProfile(real_t angularVelocity, Cell globalCell, const Vector3< real_t >& domainCenter)
 {
    // add 0.5 to get Cell's center
    const real_t velocityX = -angularVelocity * ((real_c(globalCell.y()) + real_c(0.5)) - domainCenter[0]);
@@ -84,21 +88,19 @@ int main(int argc, char** argv)
    WALBERLA_LOG_INFO_ON_ROOT(*walberlaEnv.config());
 
    // get block forest parameters from parameter file
-   auto blockForestParameters              = walberlaEnv.config()->getOneBlock("BlockForestParameters");
-   const Vector3< uint_t > cellsPerBlock   = blockForestParameters.getParameter< Vector3< uint_t > >("cellsPerBlock");
-   const Vector3< bool > periodicity       = blockForestParameters.getParameter< Vector3< bool > >("periodicity");
-   const uint_t loadBalancingFrequency     = blockForestParameters.getParameter< uint_t >("loadBalancingFrequency");
-   const bool printLoadBalancingStatistics = blockForestParameters.getParameter< bool >("printLoadBalancingStatistics");
+   auto blockForestParameters            = walberlaEnv.config()->getOneBlock("BlockForestParameters");
+   const Vector3< uint_t > cellsPerBlock = blockForestParameters.getParameter< Vector3< uint_t > >("cellsPerBlock");
+   const Vector3< bool > periodicity     = blockForestParameters.getParameter< Vector3< bool > >("periodicity");
 
    // get domain parameters from parameter file
    auto domainParameters    = walberlaEnv.config()->getOneBlock("DomainParameters");
    const uint_t domainWidth = domainParameters.getParameter< uint_t >("domainWidth");
 
-   const real_t diskRadius = real_c(domainWidth) * real_c(0.15);
+   const real_t diskRadius = real_c(domainWidth) * real_c(0.125);
    const Vector3< real_t > diskCenter =
       Vector3< real_t >(real_c(domainWidth) * real_c(0.5), real_c(domainWidth) * real_c(0.75), real_c(0.5));
-   const real_t diskSlotLength = real_c(0.25) * real_c(domainWidth);
-   const real_t diskSlotWidth  = real_c(0.05) * real_c(domainWidth);
+   const real_t diskSlotLength = real_c(2) * diskRadius - real_c(0.1) * real_c(domainWidth);
+   const real_t diskSlotWidth  = real_c(0.06) * real_c(domainWidth);
 
    // define domain size
    Vector3< uint_t > domainSize;
@@ -115,7 +117,7 @@ int main(int argc, char** argv)
    numBlocks[2] = uint_c(std::ceil(real_c(domainSize[2]) / real_c(cellsPerBlock[2])));
 
    // get number of (MPI) processes
-   uint_t numProcesses = uint_c(MPIManager::instance()->numProcesses());
+   const uint_t numProcesses = uint_c(MPIManager::instance()->numProcesses());
    WALBERLA_CHECK_LESS_EQUAL(numProcesses, numBlocks[0] * numBlocks[1] * numBlocks[2],
                              "The number of MPI processes is greater than the number of blocks as defined by "
                              "\"domainSize/cellsPerBlock\". This would result in unused MPI processes. Either decrease "
@@ -127,79 +129,68 @@ int main(int argc, char** argv)
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(numBlocks);
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(domainWidth);
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(periodicity);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(loadBalancingFrequency);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(printLoadBalancingStatistics);
+
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(diskRadius);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(diskCenter);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(diskSlotLength);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(diskSlotWidth);
 
    // get physics parameters from parameter file
-   auto physicsParameters = walberlaEnv.config()->getOneBlock("PhysicsParameters");
-   const uint_t timesteps = physicsParameters.getParameter< uint_t >("timesteps");
+   auto physicsParameters             = walberlaEnv.config()->getOneBlock("PhysicsParameters");
+   const uint_t timesteps             = physicsParameters.getParameter< uint_t >("timesteps");
+   const uint_t timestepsFullRotation = physicsParameters.getParameter< uint_t >("timestepsFullRotation");
 
-   const real_t relaxationRate           = physicsParameters.getParameter< real_t >("relaxationRate");
-   const CollisionModel_T collisionModel = CollisionModel_T(relaxationRate);
-   const real_t viscosity                = collisionModel.viscosity();
+   // compute CFL number
+   const real_t dx_SI = real_c(4) / real_c(domainWidth);
+   const real_t dt_SI = real_c(12.59652) / real_c(timestepsFullRotation);
+   const real_t CFL   = dt_SI / dx_SI; // with velocity_SI = 1
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(CFL);
 
-   const real_t reynoldsNumber = physicsParameters.getParameter< real_t >("reynoldsNumber");
-   const real_t angularVelocity =
-      reynoldsNumber * viscosity / (real_c(0.5) * real_c(domainWidth) * real_c(domainWidth));
-   const Vector3< real_t > force(real_c(0), real_c(0), real_c(0));
+   // dummy collision model (LBM not simulated in this benchmark)
+   const CollisionModel_T collisionModel = CollisionModel_T(real_c(1));
 
-   const bool enableWetting  = physicsParameters.getParameter< bool >("enableWetting");
-   const real_t contactAngle = physicsParameters.getParameter< real_t >("contactAngle");
+   const real_t angularVelocity = real_c(2) * math::pi / real_c(timestepsFullRotation);
 
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(reynoldsNumber);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(relaxationRate);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(enableWetting);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(contactAngle);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(timestepsFullRotation);
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(timesteps);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(viscosity);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(force);
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(angularVelocity);
-   WALBERLA_LOG_DEVEL_ON_ROOT("Timesteps for full rotation " << real_c(2) * math::pi / angularVelocity);
 
    // read model parameters from parameter file
    const auto modelParameters               = walberlaEnv.config()->getOneBlock("ModelParameters");
    const std::string pdfReconstructionModel = modelParameters.getParameter< std::string >("pdfReconstructionModel");
-   const std::string pdfRefillingModel      = modelParameters.getParameter< std::string >("pdfRefillingModel");
    const std::string excessMassDistributionModel =
       modelParameters.getParameter< std::string >("excessMassDistributionModel");
    const std::string curvatureModel          = modelParameters.getParameter< std::string >("curvatureModel");
    const bool useSimpleMassExchange          = modelParameters.getParameter< bool >("useSimpleMassExchange");
    const real_t cellConversionThreshold      = modelParameters.getParameter< real_t >("cellConversionThreshold");
    const real_t cellConversionForceThreshold = modelParameters.getParameter< real_t >("cellConversionForceThreshold");
-   const bool enableBubbleModel              = modelParameters.getParameter< bool >("enableBubbleModel");
-   const bool enableBubbleSplits             = modelParameters.getParameter< bool >("enableBubbleSplits");
 
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(pdfReconstructionModel);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(pdfRefillingModel);
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(excessMassDistributionModel);
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(curvatureModel);
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(useSimpleMassExchange);
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(cellConversionThreshold);
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(cellConversionForceThreshold);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(enableBubbleModel);
-   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(enableBubbleSplits);
 
    // read evaluation parameters from parameter file
    const auto evaluationParameters      = walberlaEnv.config()->getOneBlock("EvaluationParameters");
    const uint_t performanceLogFrequency = evaluationParameters.getParameter< uint_t >("performanceLogFrequency");
+   const uint_t evaluationFrequency     = evaluationParameters.getParameter< uint_t >("evaluationFrequency");
 
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(performanceLogFrequency);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(evaluationFrequency);
 
    // create non-uniform block forest (non-uniformity required for load balancing)
    const std::shared_ptr< StructuredBlockForest > blockForest =
       createNonUniformBlockForest(domainSize, cellsPerBlock, numBlocks, periodicity);
 
-   // add force field
-   const BlockDataID forceDensityFieldID =
-      field::addToStorage< VectorField_T >(blockForest, "Force field", force, field::fzyx, uint_c(1));
-
    // create lattice model
-   const LatticeModel_T latticeModel = LatticeModel_T(collisionModel, ForceModel_T(forceDensityFieldID));
+   const LatticeModel_T latticeModel = LatticeModel_T(collisionModel);
 
    // add pdf field
    const BlockDataID pdfFieldID = lbm::addPdfFieldToStorage(blockForest, "PDF field", latticeModel, field::fzyx);
 
-   // add fill level field (initialized with 0, i.e., gas everywhere)
+   // add fill level field (initialized with 1, i.e., liquid everywhere)
    const BlockDataID fillFieldID =
       field::addToStorage< ScalarField_T >(blockForest, "Fill level field", real_c(1.0), field::fzyx, uint_c(2));
 
@@ -212,10 +203,9 @@ int main(int argc, char** argv)
    // initialize the velocity profile
    for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt)
    {
-      PdfField_T* const pdfField   = blockIt->getData< PdfField_T >(pdfFieldID);
-      FlagField_T* const flagField = blockIt->getData< FlagField_T >(flagFieldID);
+      PdfField_T* const pdfField = blockIt->getData< PdfField_T >(pdfFieldID);
 
-      WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, {
+      WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, {
          //  cell in block-local coordinates
          const Cell localCell = pdfFieldIt.cell();
 
@@ -252,66 +242,57 @@ int main(int argc, char** argv)
    freeSurfaceBoundaryHandling->initFlagsFromFillLevel();
 
    // communication after initialization
-   Communication_T communication(blockForest, flagFieldID, fillFieldID, forceDensityFieldID);
+   Communication_T communication(blockForest, flagFieldID, fillFieldID);
    communication();
 
    PdfCommunication_T pdfCommunication(blockForest, pdfFieldID);
    pdfCommunication();
 
-   // add bubble model
-   std::shared_ptr< bubble_model::BubbleModelBase > bubbleModel = nullptr;
-   if (enableBubbleModel)
-   {
-      const std::shared_ptr< bubble_model::BubbleModel< LatticeModelStencil_T > > bubbleModelDerived =
-         std::make_shared< bubble_model::BubbleModel< LatticeModelStencil_T > >(blockForest, enableBubbleSplits);
-      bubbleModelDerived->initFromFillLevelField(fillFieldID);
-
-      bubbleModel = std::static_pointer_cast< bubble_model::BubbleModelBase >(bubbleModelDerived);
-   }
-   else { bubbleModel = std::make_shared< bubble_model::BubbleModelConstantPressure >(real_c(1)); }
+   const ConstBlockDataID initialFillFieldID =
+      field::addCloneToStorage< ScalarField_T >(blockForest, fillFieldID, "Initial fill level field");
 
-   // set density in non-liquid or non-interface cells to one (after initializing with hydrostatic pressure)
-   setDensityInNonFluidCellsToOne< FlagField_T, PdfField_T >(blockForest, flagInfo, flagFieldID, pdfFieldID);
+   // add bubble model
+   const std::shared_ptr< bubble_model::BubbleModelBase > bubbleModel =
+      std::make_shared< bubble_model::BubbleModelConstantPressure >(real_c(1));
 
    // create timeloop
    SweepTimeloop timeloop(blockForest, timesteps);
 
-   const real_t surfaceTension = real_c(0);
-
-   // Laplace pressure = 2 * surface tension * curvature; curvature computation is not necessary with zero surface
-   // tension
-   bool computeCurvature = false;
-   if (!realIsEqual(surfaceTension, real_c(0), real_c(1e-14))) { computeCurvature = true; }
-
    // add surface geometry handler
    const SurfaceGeometryHandler< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T > geometryHandler(
-      blockForest, freeSurfaceBoundaryHandling, fillFieldID, curvatureModel, computeCurvature, enableWetting,
-      contactAngle);
+      blockForest, freeSurfaceBoundaryHandling, fillFieldID, curvatureModel, false, false, real_c(0));
 
    geometryHandler.addSweeps(timeloop);
 
    // get fields created by surface geometry handler
-   const ConstBlockDataID curvatureFieldID = geometryHandler.getConstCurvatureFieldID();
-   const ConstBlockDataID normalFieldID    = geometryHandler.getConstNormalFieldID();
+   const ConstBlockDataID normalFieldID = geometryHandler.getConstNormalFieldID();
 
    // add boundary handling for standard boundaries and free surface boundaries
-   const SurfaceDynamicsHandler< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T > dynamicsHandler(
-      blockForest, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID, normalFieldID, curvatureFieldID,
-      freeSurfaceBoundaryHandling, bubbleModel, pdfReconstructionModel, pdfRefillingModel, excessMassDistributionModel,
-      relaxationRate, force, surfaceTension, useSimpleMassExchange, cellConversionThreshold,
+   const AdvectionDynamicsHandler< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T > dynamicsHandler(
+      blockForest, pdfFieldID, flagFieldID, fillFieldID, normalFieldID, freeSurfaceBoundaryHandling, bubbleModel,
+      pdfReconstructionModel, excessMassDistributionModel, useSimpleMassExchange, cellConversionThreshold,
       cellConversionForceThreshold);
 
    dynamicsHandler.addSweeps(timeloop);
 
-   // add load balancing
-   const LoadBalancer< FlagField_T, CommunicationStencil_T, LatticeModelStencil_T > loadBalancer(
-      blockForest, communication, pdfCommunication, bubbleModel, uint_c(50), uint_c(10), uint_c(5),
-      loadBalancingFrequency, printLoadBalancingStatistics);
-   timeloop.addFuncAfterTimeStep(loadBalancer, "Sweep: load balancing");
+   // add evaluator for geometrical
+   const std::shared_ptr< real_t > geometricalError = std::make_shared< real_t >(real_c(0));
+   const GeometricalErrorEvaluator< FreeSurfaceBoundaryHandling_T, FlagField_T, ScalarField_T >
+      geometricalErrorEvaluator(blockForest, freeSurfaceBoundaryHandling, initialFillFieldID, fillFieldID,
+                                evaluationFrequency, geometricalError);
+   timeloop.addFuncAfterTimeStep(geometricalErrorEvaluator, "Evaluator: geometrical errors");
+
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
 
    // add VTK output
    addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
-      blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID,
+      blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, BlockDataID(),
       geometryHandler.getCurvatureFieldID(), geometryHandler.getNormalFieldID(),
       geometryHandler.getObstNormalFieldID());
 
@@ -332,6 +313,12 @@ int main(int argc, char** argv)
    {
       timeloop.singleStep(timingPool, true);
 
+      if (t % evaluationFrequency == uint_c(0))
+      {
+         WALBERLA_LOG_DEVEL_ON_ROOT("time step = " << t << "\n\t\ttotal mass = " << *totalMass << "\n\t\texcess mass = "
+                                                   << *excessMass << "\n\t\tgeometrical error = " << *geometricalError);
+      }
+
       // set the constant velocity profile
       for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt)
       {
@@ -339,22 +326,20 @@ int main(int argc, char** argv)
          FlagField_T* const flagField = blockIt->getData< FlagField_T >(flagFieldID);
 
          WALBERLA_FOR_ALL_CELLS(pdfFieldIt, pdfField, flagFieldIt, flagField, {
-            if (flagInfo.isInterface(flagFieldIt) || flagInfo.isLiquid(flagFieldIt))
-            {
-               //  cell in block-local coordinates
-               const Cell localCell = pdfFieldIt.cell();
-
-               // get cell in global coordinates
-               Cell globalCell = pdfFieldIt.cell();
-               blockForest->transformBlockLocalToGlobalCell(globalCell, *blockIt, localCell);
-
-               // set velocity profile
-               const Vector3< real_t > initialVelocity = velocityProfile(angularVelocity, globalCell, domainCenter);
-               pdfField->setDensityAndVelocity(localCell, initialVelocity, real_c(1));
-            }
+            const Cell localCell = pdfFieldIt.cell();
+
+            // get cell in global coordinates
+            Cell globalCell = pdfFieldIt.cell();
+            blockForest->transformBlockLocalToGlobalCell(globalCell, *blockIt, localCell);
+
+            // set velocity profile
+            const Vector3< real_t > initialVelocity = velocityProfile(angularVelocity, globalCell, domainCenter);
+            pdfField->setDensityAndVelocity(localCell, initialVelocity, real_c(1));
          }) // WALBERLA_FOR_ALL_CELLS
       }
 
+      pdfCommunication();
+
       if (t % performanceLogFrequency == uint_c(0) && t > uint_c(0)) { timingPool.logResultOnRoot(); }
    }
 
diff --git a/apps/showcases/FreeSurface/ZalesakDisk.prm b/apps/benchmarks/FreeSurfaceAdvection/ZalesakDisk.prm
similarity index 63%
rename from apps/showcases/FreeSurface/ZalesakDisk.prm
rename to apps/benchmarks/FreeSurfaceAdvection/ZalesakDisk.prm
index b9af1280c..857f1510b 100644
--- a/apps/showcases/FreeSurface/ZalesakDisk.prm
+++ b/apps/benchmarks/FreeSurfaceAdvection/ZalesakDisk.prm
@@ -1,53 +1,46 @@
 BlockForestParameters
 {
-   cellsPerBlock                 < 50, 50, 1 >;
-   periodicity                   < 0, 0, 1 >;
-   loadBalancingFrequency        0;
-   printLoadBalancingStatistics  false;
+   cellsPerBlock                 < 100, 100, 1 >;
+   periodicity                   < 0, 0, 0 >;
 }
 
 DomainParameters
 {
-   domainWidth       100;
+   domainWidth       200;
 }
 
 PhysicsParameters
 {
-   reynoldsNumber    100; // Re = angularVelocity * domainWidth * 0.5 * domainWidth / kin. viscosity
-   relaxationRate    1.8;
-   enableWetting     false;
-   contactAngle      0; // only used if enableWetting=true
-   timesteps         1000000;
+   timestepsFullRotation 12570;  // angularVelocity = 2 * pi / timestepsFullRotation
+   timesteps             12571;
+
 }
 
 ModelParameters
 {
    pdfReconstructionModel        OnlyMissing;
-   pdfRefillingModel             EquilibriumRefilling;
    excessMassDistributionModel   EvenlyAllInterface;
    curvatureModel                FiniteDifferenceMethod;
    useSimpleMassExchange         false;
    cellConversionThreshold       1e-2;
    cellConversionForceThreshold  1e-1;
-
-   enableBubbleModel             True;
-   enableBubbleSplits            false; // only used if enableBubbleModel=true
 }
 
 EvaluationParameters
 {
+   evaluationFrequency 12570;
    performanceLogFrequency 10000;
 }
 
 BoundaryParameters
 {
    // X
-   Border { direction W;  walldistance -1; FreeSlip{} }
-   Border { direction E;  walldistance -1; FreeSlip{} }
+   //Border { direction W;  walldistance -1; FreeSlip{} }
+   //Border { direction E;  walldistance -1; FreeSlip{} }
 
    // Y
-   Border { direction N;  walldistance -1; FreeSlip{} }
-   Border { direction S;  walldistance -1; FreeSlip{} }
+   //Border { direction N;  walldistance -1; FreeSlip{} }
+   //Border { direction S;  walldistance -1; FreeSlip{} }
 
    // Z
    //Border { direction T;  walldistance -1; FreeSlip{} }
@@ -64,7 +57,7 @@ VTK
 {
    fluid_field
    {
-      writeFrequency       4241;
+      writeFrequency       12570;
       ghostLayers          0;
       baseFolder           vtk-out;
       samplingResolution   1;
@@ -80,7 +73,6 @@ VTK
          //obstacle_normal;
          //pdf;
          //flag;
-         //force_density;
       }
 
       inclusion_filters
diff --git a/apps/benchmarks/FreeSurfaceAdvection/functionality/AdvectSweep.h b/apps/benchmarks/FreeSurfaceAdvection/functionality/AdvectSweep.h
new file mode 100644
index 000000000..80db158e6
--- /dev/null
+++ b/apps/benchmarks/FreeSurfaceAdvection/functionality/AdvectSweep.h
@@ -0,0 +1,152 @@
+//======================================================================================================================
+//
+//  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 AdvectSweep.h
+//! \ingroup free_surface
+//! \author Martin Bauer
+//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de>
+//! \brief Sweep for mass advection and interface cell conversion marking (simplified StreamReconstructAdvectSweep).
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/DataTypes.h"
+#include "core/math/Vector3.h"
+#include "core/mpi/Reduce.h"
+#include "core/timing/TimingPool.h"
+
+#include "field/FieldClone.h"
+#include "field/FlagField.h"
+
+#include "lbm/free_surface/FlagInfo.h"
+#include "lbm/free_surface/bubble_model/BubbleModel.h"
+#include "lbm/free_surface/dynamics/PdfReconstructionModel.h"
+#include "lbm/free_surface/dynamics/functionality/AdvectMass.h"
+#include "lbm/free_surface/dynamics/functionality/FindInterfaceCellConversion.h"
+#include "lbm/free_surface/dynamics/functionality/GetOredNeighborhood.h"
+#include "lbm/free_surface/dynamics/functionality/ReconstructInterfaceCellABB.h"
+#include "lbm/sweeps/StreamPull.h"
+
+namespace walberla
+{
+namespace free_surface
+{
+template< typename LatticeModel_T, typename BoundaryHandling_T, typename FlagField_T, typename FlagInfo_T,
+          typename ScalarField_T, typename VectorField_T >
+class AdvectSweep
+{
+ public:
+   using flag_t     = typename FlagInfo_T::flag_t;
+   using PdfField_T = lbm::PdfField< LatticeModel_T >;
+
+   AdvectSweep(BlockDataID handlingID, BlockDataID fillFieldID, BlockDataID flagFieldID, BlockDataID pdfField,
+               const FlagInfo_T& flagInfo, const PdfReconstructionModel& pdfReconstructionModel,
+               bool useSimpleMassExchange, real_t cellConversionThreshold, real_t cellConversionForceThreshold)
+      : handlingID_(handlingID), fillFieldID_(fillFieldID), flagFieldID_(flagFieldID), pdfFieldID_(pdfField),
+        flagInfo_(flagInfo), neighborhoodFlagFieldClone_(flagFieldID), fillFieldClone_(fillFieldID),
+        pdfFieldClone_(pdfField), pdfReconstructionModel_(pdfReconstructionModel),
+        useSimpleMassExchange_(useSimpleMassExchange), cellConversionThreshold_(cellConversionThreshold),
+        cellConversionForceThreshold_(cellConversionForceThreshold)
+   {}
+
+   void operator()(IBlock* const block);
+
+ protected:
+   BlockDataID handlingID_;
+   BlockDataID fillFieldID_;
+   BlockDataID flagFieldID_;
+   BlockDataID pdfFieldID_;
+
+   FlagInfo_T flagInfo_;
+
+   // efficient clones of fields to provide temporary fields (for writing to)
+   field::FieldClone< FlagField_T, true > neighborhoodFlagFieldClone_;
+   field::FieldClone< ScalarField_T, true > fillFieldClone_;
+   field::FieldClone< PdfField_T, true > pdfFieldClone_;
+
+   PdfReconstructionModel pdfReconstructionModel_;
+   bool useSimpleMassExchange_;
+   real_t cellConversionThreshold_;
+   real_t cellConversionForceThreshold_;
+}; // class AdvectSweep
+
+template< typename LatticeModel_T, typename BoundaryHandling_T, typename FlagField_T, typename FlagInfo_T,
+          typename ScalarField_T, typename VectorField_T >
+void AdvectSweep< LatticeModel_T, BoundaryHandling_T, FlagField_T, FlagInfo_T, ScalarField_T,
+                  VectorField_T >::operator()(IBlock* const block)
+{
+   // fetch data
+   ScalarField_T* const fillSrcField = block->getData< ScalarField_T >(fillFieldID_);
+   PdfField_T* const pdfSrcField     = block->getData< PdfField_T >(pdfFieldID_);
+
+   FlagField_T* const flagField = block->getData< FlagField_T >(flagFieldID_);
+
+   // temporary fields that act as destination fields
+   FlagField_T* const neighborhoodFlagField = neighborhoodFlagFieldClone_.get(block);
+   ScalarField_T* const fillDstField        = fillFieldClone_.get(block);
+
+   // combine all neighbor flags using bitwise OR and write them to the neighborhood field
+   // this is simply a pre-computation of often required values
+   // IMPORTANT REMARK: the "OredNeighborhood" is also required for each cell in the first ghost layer; this requires
+   // access to all first ghost layer cell's neighbors (i.e., to the second ghost layer)
+   WALBERLA_CHECK_GREATER_EQUAL(flagField->nrOfGhostLayers(), uint_c(2));
+   getOredNeighborhood< typename LatticeModel_T::Stencil >(flagField, neighborhoodFlagField);
+
+   // explicitly avoid OpenMP due to bubble model update (reportFillLevelChange)
+   WALBERLA_FOR_ALL_CELLS_OMP(
+      pdfSrcFieldIt, pdfSrcField, fillSrcFieldIt, fillSrcField, fillDstFieldIt, fillDstField, flagFieldIt, flagField,
+      neighborhoodFlagFieldIt, neighborhoodFlagField, omp critical, {
+         if (flagInfo_.isInterface(flagFieldIt))
+         {
+            const real_t rho = lbm::getDensity(pdfSrcField->latticeModel(), pdfSrcFieldIt);
+
+            // compute mass advection using post-collision PDFs (explicitly not PDFs updated by stream above)
+            const real_t deltaMass =
+               (advectMass< LatticeModel_T, FlagField_T, typename ScalarField_T::iterator,
+                            typename PdfField_T::iterator, typename FlagField_T::iterator,
+                            typename FlagField_T::iterator, FlagInfo_T >) (flagField, fillSrcFieldIt, pdfSrcFieldIt,
+                                                                           flagFieldIt, neighborhoodFlagFieldIt,
+                                                                           flagInfo_, useSimpleMassExchange_);
+
+            // update fill level after LBM stream and mass exchange
+            *fillDstFieldIt = *fillSrcFieldIt + deltaMass / rho;
+         }
+         else // treat non-interface cells
+         {
+            // manually adjust the fill level to avoid outdated fill levels being copied from fillSrcField
+            if (flagInfo_.isGas(flagFieldIt)) { *fillDstFieldIt = real_c(0); }
+            else
+            {
+               if (flagInfo_.isLiquid(flagFieldIt)) { *fillDstFieldIt = real_c(1); }
+               else // flag is e.g. obstacle or outflow
+               {
+                  *fillDstFieldIt = *fillSrcFieldIt;
+               }
+            }
+         }
+      }) // WALBERLA_FOR_ALL_CELLS_XYZ_OMP
+
+   fillSrcField->swapDataPointers(fillDstField);
+
+   BoundaryHandling_T* const handling = block->getData< BoundaryHandling_T >(handlingID_);
+
+   // mark interface cell for conversion
+   findInterfaceCellConversions< LatticeModel_T >(handling, fillSrcField, flagField, neighborhoodFlagField, flagInfo_,
+                                                  cellConversionThreshold_, cellConversionForceThreshold_);
+}
+
+} // namespace free_surface
+} // namespace walberla
diff --git a/apps/benchmarks/FreeSurfaceAdvection/functionality/AdvectionDynamicsHandler.h b/apps/benchmarks/FreeSurfaceAdvection/functionality/AdvectionDynamicsHandler.h
new file mode 100644
index 000000000..1e48a1683
--- /dev/null
+++ b/apps/benchmarks/FreeSurfaceAdvection/functionality/AdvectionDynamicsHandler.h
@@ -0,0 +1,254 @@
+//======================================================================================================================
+//
+//  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 AdvectionDynamicsHandler.h
+//! \ingroup surface_dynamics
+//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de>
+//! \brief Handles free surface advection (without LBM flow simulation; this is a simplified SurfaceDynamicsHandler).
+//
+//======================================================================================================================
+
+#pragma once
+
+#include "core/DataTypes.h"
+
+#include "domain_decomposition/StructuredBlockStorage.h"
+
+#include "field/AddToStorage.h"
+#include "field/FlagField.h"
+
+#include "lbm/blockforest/communication/SimpleCommunication.h"
+#include "lbm/blockforest/communication/UpdateSecondGhostLayer.h"
+#include "lbm/free_surface/BlockStateDetectorSweep.h"
+#include "lbm/free_surface/FlagInfo.h"
+#include "lbm/free_surface/boundary/FreeSurfaceBoundaryHandling.h"
+#include "lbm/free_surface/bubble_model/BubbleModel.h"
+#include "lbm/free_surface/dynamics/CellConversionSweep.h"
+#include "lbm/free_surface/dynamics/ConversionFlagsResetSweep.h"
+#include "lbm/free_surface/dynamics/ExcessMassDistributionModel.h"
+#include "lbm/free_surface/dynamics/ExcessMassDistributionSweep.h"
+#include "lbm/free_surface/dynamics/PdfReconstructionModel.h"
+#include "lbm/sweeps/CellwiseSweep.h"
+#include "lbm/sweeps/SweepWrappers.h"
+
+#include "stencil/D3Q27.h"
+
+#include "timeloop/SweepTimeloop.h"
+
+#include "AdvectSweep.h"
+
+namespace walberla
+{
+namespace free_surface
+{
+template< typename LatticeModel_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T >
+class AdvectionDynamicsHandler
+{
+ protected:
+   using Communication_T = blockforest::SimpleCommunication< typename LatticeModel_T::Stencil >;
+
+   // communication in corner directions (D2Q9/D3Q27) is required for all fields but the PDF field
+   using CommunicationStencil_T =
+      typename std::conditional< LatticeModel_T::Stencil::D == uint_t(2), stencil::D2Q9, stencil::D3Q27 >::type;
+   using CommunicationCorner_T = blockforest::SimpleCommunication< CommunicationStencil_T >;
+
+   using FreeSurfaceBoundaryHandling_T = FreeSurfaceBoundaryHandling< LatticeModel_T, FlagField_T, ScalarField_T >;
+
+ public:
+   AdvectionDynamicsHandler(const std::shared_ptr< StructuredBlockForest >& blockForest, BlockDataID pdfFieldID,
+                            BlockDataID flagFieldID, BlockDataID fillFieldID, ConstBlockDataID normalFieldID,
+                            const std::shared_ptr< FreeSurfaceBoundaryHandling_T >& freeSurfaceBoundaryHandling,
+                            const std::shared_ptr< BubbleModelBase >& bubbleModel,
+                            const std::string& pdfReconstructionModel, const std::string& excessMassDistributionModel,
+                            bool useSimpleMassExchange, real_t cellConversionThreshold,
+                            real_t cellConversionForceThreshold)
+      : blockForest_(blockForest), pdfFieldID_(pdfFieldID), flagFieldID_(flagFieldID), fillFieldID_(fillFieldID),
+        normalFieldID_(normalFieldID), bubbleModel_(bubbleModel),
+        freeSurfaceBoundaryHandling_(freeSurfaceBoundaryHandling), pdfReconstructionModel_(pdfReconstructionModel),
+        excessMassDistributionModel_({ excessMassDistributionModel }), useSimpleMassExchange_(useSimpleMassExchange),
+        cellConversionThreshold_(cellConversionThreshold), cellConversionForceThreshold_(cellConversionForceThreshold)
+   {
+      WALBERLA_CHECK(LatticeModel_T::compressible,
+                     "The free surface lattice Boltzmann extension works only with compressible LBM models.");
+
+      if (excessMassDistributionModel_.isEvenlyAllInterfaceFallbackLiquidType())
+      {
+         // add additional field for storing excess mass in liquid cells
+         excessMassFieldID_ =
+            field::addToStorage< ScalarField_T >(blockForest_, "Excess mass", real_c(0), field::fzyx, uint_c(1));
+      }
+   }
+
+   ConstBlockDataID getConstExcessMassFieldID() const { return excessMassFieldID_; }
+
+   /********************************************************************************************************************
+    * The order of these sweeps is similar to page 40 in the dissertation of T. Pohl, 2008.
+    *******************************************************************************************************************/
+   void addSweeps(SweepTimeloop& timeloop) const
+   {
+      using StateSweep = BlockStateDetectorSweep< FlagField_T >;
+
+      const auto& flagInfo = freeSurfaceBoundaryHandling_->getFlagInfo();
+
+      const auto blockStateUpdate = StateSweep(blockForest_, flagInfo, flagFieldID_);
+
+      // empty sweeps required for using selectors (e.g. StateSweep::onlyGasAndBoundary)
+      const auto emptySweep = [](IBlock*) {};
+
+      // sweep for
+      // - reconstruction of PDFs in interface cells
+      // - streaming of PDFs in interface cells (and liquid cells on the same block)
+      // - advection of mass
+      // - update bubble volumes
+      // - marking interface cells for conversion
+      const AdvectSweep< LatticeModel_T, typename FreeSurfaceBoundaryHandling_T::BoundaryHandling_T, FlagField_T,
+                         typename FreeSurfaceBoundaryHandling_T::FlagInfo_T, ScalarField_T, VectorField_T >
+         advectSweep(freeSurfaceBoundaryHandling_->getHandlingID(), fillFieldID_, flagFieldID_, pdfFieldID_, flagInfo,
+                     pdfReconstructionModel_, useSimpleMassExchange_, cellConversionThreshold_,
+                     cellConversionForceThreshold_);
+      // sweep acts only on blocks with at least one interface cell (due to StateSweep::fullFreeSurface)
+      timeloop.add()
+         << Sweep(advectSweep, "Sweep: Advect", StateSweep::fullFreeSurface)
+         << Sweep(emptySweep, "Empty sweep: Advect")
+         // do not communicate PDFs here:
+         // - stream on blocks with "StateSweep::fullFreeSurface" was performed here using post-collision PDFs
+         // - stream on other blocks is performed below and should also use post-collision PDFs
+         // => if PDFs were communicated here, the ghost layer of other blocks would have post-stream PDFs
+         << AfterFunction(CommunicationCorner_T(blockForest_, fillFieldID_, flagFieldID_),
+                          "Communication: after Advect sweep")
+         << AfterFunction(blockforest::UpdateSecondGhostLayer< ScalarField_T >(blockForest_, fillFieldID_),
+                          "Second ghost layer update: after Advect sweep (fill level field)")
+         << AfterFunction(blockforest::UpdateSecondGhostLayer< FlagField_T >(blockForest_, flagFieldID_),
+                          "Second ghost layer update: after Advect sweep (flag field)");
+
+      // convert cells
+      // - according to the flags from StreamReconstructAdvectSweep (interface -> gas/liquid)
+      // - to ensure a closed layer of interface cells (gas/liquid -> interface)
+      // - detect and register bubble merges/splits (bubble volumes are already updated in StreamReconstructAdvectSweep)
+      // - convert cells and initialize PDFs near inflow boundaries
+      const CellConversionSweep< LatticeModel_T, typename FreeSurfaceBoundaryHandling_T::BoundaryHandling_T,
+                                 ScalarField_T >
+         cellConvSweep(freeSurfaceBoundaryHandling_->getHandlingID(), pdfFieldID_, flagInfo, bubbleModel_.get());
+      timeloop.add() << Sweep(cellConvSweep, "Sweep: cell conversion", StateSweep::fullFreeSurface)
+                     << Sweep(emptySweep, "Empty sweep: cell conversion")
+                     << AfterFunction(Communication_T(blockForest_, pdfFieldID_),
+                                      "Communication: after cell conversion sweep (PDF field)")
+                     // communicate the flag field also in corner directions
+                     << AfterFunction(CommunicationCorner_T(blockForest_, flagFieldID_),
+                                      "Communication: after cell conversion sweep (flag field)")
+                     << AfterFunction(blockforest::UpdateSecondGhostLayer< FlagField_T >(blockForest_, flagFieldID_),
+                                      "Second ghost layer update: after cell conversion sweep (flag field)");
+
+      // distribute excess mass:
+      // - excess mass: mass that is free after conversion from interface to gas/liquid cells
+      // - update the bubble model
+      if (excessMassDistributionModel_.isEvenlyType())
+      {
+         const ExcessMassDistributionSweepInterfaceEvenly< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T >
+            distributeMassSweep(excessMassDistributionModel_, fillFieldID_, flagFieldID_, pdfFieldID_, flagInfo);
+         timeloop.add() << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::fullFreeSurface)
+                        << Sweep(emptySweep, "Empty sweep: distribute excess mass")
+                        << AfterFunction(CommunicationCorner_T(blockForest_, fillFieldID_),
+                                         "Communication: after excess mass distribution sweep")
+                        << AfterFunction(
+                              blockforest::UpdateSecondGhostLayer< ScalarField_T >(blockForest_, fillFieldID_),
+                              "Second ghost layer update: after excess mass distribution sweep (fill level field)")
+                        // update bubble model, i.e., perform registered bubble merges/splits; bubble merges/splits are
+                        // already detected and registered by CellConversionSweep
+                        << AfterFunction(std::bind(&bubble_model::BubbleModelBase::update, bubbleModel_),
+                                         "Sweep: bubble model update");
+      }
+      else
+      {
+         if (excessMassDistributionModel_.isWeightedType())
+         {
+            const ExcessMassDistributionSweepInterfaceWeighted< LatticeModel_T, FlagField_T, ScalarField_T,
+                                                                VectorField_T >
+               distributeMassSweep(excessMassDistributionModel_, fillFieldID_, flagFieldID_, pdfFieldID_, flagInfo,
+                                   normalFieldID_);
+            timeloop.add() << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::fullFreeSurface)
+                           << Sweep(emptySweep, "Empty sweep: distribute excess mass")
+                           << AfterFunction(CommunicationCorner_T(blockForest_, fillFieldID_),
+                                            "Communication: after excess mass distribution sweep")
+                           << AfterFunction(
+                                 blockforest::UpdateSecondGhostLayer< ScalarField_T >(blockForest_, fillFieldID_),
+                                 "Second ghost layer update: after excess mass distribution sweep (fill level field)")
+                           // update bubble model, i.e., perform registered bubble merges/splits; bubble merges/splits
+                           // are already detected and registered by CellConversionSweep
+                           << AfterFunction(std::bind(&bubble_model::BubbleModelBase::update, bubbleModel_),
+                                            "Sweep: bubble model update");
+         }
+         else
+         {
+            if (excessMassDistributionModel_.isEvenlyAllInterfaceFallbackLiquidType())
+            {
+               const ExcessMassDistributionSweepInterfaceAndLiquid< LatticeModel_T, FlagField_T, ScalarField_T,
+                                                                    VectorField_T >
+                  distributeMassSweep(excessMassDistributionModel_, fillFieldID_, flagFieldID_, pdfFieldID_, flagInfo,
+                                      excessMassFieldID_);
+               timeloop.add()
+                  // perform this sweep also on "onlyLBM" blocks because liquid cells also exchange excess mass here
+                  << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::fullFreeSurface)
+                  << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::onlyLBM)
+                  << Sweep(emptySweep, "Empty sweep: distribute excess mass")
+                  << AfterFunction(CommunicationCorner_T(blockForest_, fillFieldID_, excessMassFieldID_),
+                                   "Communication: after excess mass distribution sweep")
+                  << AfterFunction(blockforest::UpdateSecondGhostLayer< ScalarField_T >(blockForest_, fillFieldID_),
+                                   "Second ghost layer update: after excess mass distribution sweep (fill level field)")
+                  // update bubble model, i.e., perform registered bubble merges/splits; bubble
+                  // merges/splits are already detected and registered by CellConversionSweep
+                  << AfterFunction(std::bind(&bubble_model::BubbleModelBase::update, bubbleModel_),
+                                   "Sweep: bubble model update");
+            }
+         }
+      }
+
+      // reset all flags that signal cell conversions (except "keepInterfaceForWettingFlag")
+      ConversionFlagsResetSweep< FlagField_T > resetConversionFlagsSweep(flagFieldID_, flagInfo);
+      timeloop.add() << Sweep(resetConversionFlagsSweep, "Sweep: conversion flag reset", StateSweep::fullFreeSurface)
+                     << Sweep(emptySweep, "Empty sweep: conversion flag reset")
+                     << AfterFunction(CommunicationCorner_T(blockForest_, flagFieldID_),
+                                      "Communication: after excess mass distribution sweep")
+                     << AfterFunction(blockforest::UpdateSecondGhostLayer< FlagField_T >(blockForest_, flagFieldID_),
+                                      "Second ghost layer update: after excess mass distribution sweep (flag field)");
+
+      // update block states
+      timeloop.add() << Sweep(blockStateUpdate, "Sweep: block state update");
+   }
+
+ private:
+   std::shared_ptr< StructuredBlockForest > blockForest_;
+
+   BlockDataID pdfFieldID_;
+   BlockDataID flagFieldID_;
+   BlockDataID fillFieldID_;
+
+   ConstBlockDataID normalFieldID_;
+
+   std::shared_ptr< BubbleModelBase > bubbleModel_;
+   std::shared_ptr< FreeSurfaceBoundaryHandling_T > freeSurfaceBoundaryHandling_;
+
+   PdfReconstructionModel pdfReconstructionModel_;
+   ExcessMassDistributionModel excessMassDistributionModel_;
+
+   bool useSimpleMassExchange_;
+   real_t cellConversionThreshold_;
+   real_t cellConversionForceThreshold_;
+
+   BlockDataID excessMassFieldID_ = BlockDataID();
+}; // class AdvectionDynamicsHandler
+
+} // namespace free_surface
+} // namespace walberla
\ No newline at end of file
diff --git a/apps/benchmarks/FreeSurfaceAdvection/functionality/GeometricalErrorEvaluator.h b/apps/benchmarks/FreeSurfaceAdvection/functionality/GeometricalErrorEvaluator.h
new file mode 100644
index 000000000..b0a5d7bba
--- /dev/null
+++ b/apps/benchmarks/FreeSurfaceAdvection/functionality/GeometricalErrorEvaluator.h
@@ -0,0 +1,139 @@
+//======================================================================================================================
+//
+//  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 GeometricalErrorEvaluator.h
+//! \author Christoph Schwarzmeier <christoph.schwarzmeier@fau.de>
+//! \brief Compute the geometrical error in free-surface advection test cases.
+//======================================================================================================================
+
+#include "blockforest/StructuredBlockForest.h"
+
+#include "core/DataTypes.h"
+
+#include "domain_decomposition/BlockDataID.h"
+
+#include "field/iterators/IteratorMacros.h"
+
+namespace walberla
+{
+namespace free_surface
+{
+template< typename FreeSurfaceBoundaryHandling_T, typename FlagField_T, typename ScalarField_T >
+class GeometricalErrorEvaluator
+{
+ public:
+   GeometricalErrorEvaluator(const std::weak_ptr< StructuredBlockForest >& blockForest,
+                             const std::weak_ptr< const FreeSurfaceBoundaryHandling_T >& freeSurfaceBoundaryHandling,
+                             const ConstBlockDataID& initialfillFieldID, const ConstBlockDataID& fillFieldID,
+                             uint_t frequency, const std::shared_ptr< real_t >& geometricalError)
+      : blockForest_(blockForest), freeSurfaceBoundaryHandling_(freeSurfaceBoundaryHandling),
+        initialFillFieldID_(initialfillFieldID), fillFieldID_(fillFieldID), frequency_(frequency),
+        geometricalError_(geometricalError), executionCounter_(uint_c(0))
+   {}
+
+   void operator()()
+   {
+      if (frequency_ == uint_c(0)) { return; }
+
+      auto blockForest = blockForest_.lock();
+      WALBERLA_CHECK_NOT_NULLPTR(blockForest);
+
+      auto freeSurfaceBoundaryHandling = freeSurfaceBoundaryHandling_.lock();
+      WALBERLA_CHECK_NOT_NULLPTR(freeSurfaceBoundaryHandling);
+
+      if (executionCounter_ == uint_c(0))
+      {
+         computeInitialFillLevelSum(blockForest, freeSurfaceBoundaryHandling);
+         computeError(blockForest, freeSurfaceBoundaryHandling);
+      }
+      else
+      {
+         // only evaluate in given frequencies
+         if (executionCounter_ % frequency_ == uint_c(0)) { computeError(blockForest, freeSurfaceBoundaryHandling); }
+      }
+
+      ++executionCounter_;
+   }
+
+   void computeInitialFillLevelSum(
+      const std::shared_ptr< const StructuredBlockForest >& blockForest,
+      const std::shared_ptr< const FreeSurfaceBoundaryHandling_T >& freeSurfaceBoundaryHandling)
+   {
+      const BlockDataID flagFieldID = freeSurfaceBoundaryHandling->getFlagFieldID();
+      const typename FreeSurfaceBoundaryHandling_T::FlagInfo_T& flagInfo = freeSurfaceBoundaryHandling->getFlagInfo();
+
+      for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt)
+      {
+         const ScalarField_T* const initialfillField = blockIt->getData< const ScalarField_T >(initialFillFieldID_);
+         const FlagField_T* const flagField          = blockIt->getData< const FlagField_T >(flagFieldID);
+
+         // avoid OpenMP here because initialFillLevelSum_ is a class member and not a regular variable
+         WALBERLA_FOR_ALL_CELLS_OMP(initialfillFieldIt, initialfillField, flagFieldIt, flagField, omp critical, {
+            if (flagInfo.isInterface(flagFieldIt) || flagInfo.isLiquid(flagFieldIt))
+            {
+               initialFillLevelSum_ += *initialfillFieldIt;
+            }
+         }) // WALBERLA_FOR_ALL_CELLS
+      }
+
+      mpi::allReduceInplace< real_t >(initialFillLevelSum_, mpi::SUM);
+   }
+
+   void computeError(const std::shared_ptr< const StructuredBlockForest >& blockForest,
+                     const std::shared_ptr< const FreeSurfaceBoundaryHandling_T >& freeSurfaceBoundaryHandling)
+   {
+      const BlockDataID flagFieldID = freeSurfaceBoundaryHandling->getFlagFieldID();
+      const typename FreeSurfaceBoundaryHandling_T::FlagInfo_T& flagInfo = freeSurfaceBoundaryHandling->getFlagInfo();
+
+      real_t geometricalError = real_c(0);
+      real_t fillLevelSum     = real_c(0);
+
+      for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt)
+      {
+         const ScalarField_T* const initialfillField = blockIt->getData< const ScalarField_T >(initialFillFieldID_);
+         const ScalarField_T* const fillField        = blockIt->getData< const ScalarField_T >(fillFieldID_);
+         const FlagField_T* const flagField          = blockIt->getData< const FlagField_T >(flagFieldID);
+
+         WALBERLA_FOR_ALL_CELLS_OMP(initialfillFieldIt, initialfillField, fillFieldIt, fillField, flagFieldIt,
+                                    flagField, omp parallel for schedule(static) reduction(+:geometricalError)
+                                                                                 reduction(+:fillLevelSum), {
+            if (flagInfo.isInterface(flagFieldIt) || flagInfo.isLiquid(flagFieldIt))
+            {
+               geometricalError += real_c(std::abs(*initialfillFieldIt - *fillFieldIt));
+               fillLevelSum += *fillFieldIt;
+            }
+         }) // WALBERLA_FOR_ALL_CELLS
+      }
+
+      mpi::allReduceInplace< real_t >(geometricalError, mpi::SUM);
+
+      // compute L1 norms
+      *geometricalError_ = geometricalError / initialFillLevelSum_;
+   }
+
+ private:
+   std::weak_ptr< StructuredBlockForest > blockForest_;
+   std::weak_ptr< const FreeSurfaceBoundaryHandling_T > freeSurfaceBoundaryHandling_;
+   ConstBlockDataID initialFillFieldID_;
+   ConstBlockDataID fillFieldID_;
+   uint_t frequency_;
+   std::shared_ptr< real_t > geometricalError_;
+
+   uint_t executionCounter_;
+   real_t initialFillLevelSum_ = real_c(0);
+}; // class GeometricalErrorEvaluator
+
+} // namespace free_surface
+} // namespace walberla
\ No newline at end of file
diff --git a/apps/showcases/FreeSurface/BubblyPoiseuille.cpp b/apps/showcases/FreeSurface/BubblyPoiseuille.cpp
index e27edf893..e54117c15 100644
--- a/apps/showcases/FreeSurface/BubblyPoiseuille.cpp
+++ b/apps/showcases/FreeSurface/BubblyPoiseuille.cpp
@@ -28,6 +28,7 @@
 #include "lbm/field/AddToStorage.h"
 #include "lbm/free_surface/LoadBalancing.h"
 #include "lbm/free_surface/SurfaceMeshWriter.h"
+#include "lbm/free_surface/TotalMassComputer.h"
 #include "lbm/free_surface/VtkWriter.h"
 #include "lbm/free_surface/bubble_model/Geometry.h"
 #include "lbm/free_surface/dynamics/SurfaceDynamicsHandler.h"
@@ -195,8 +196,10 @@ int main(int argc, char** argv)
    // read evaluation parameters from parameter file
    const auto evaluationParameters      = walberlaEnv.config()->getOneBlock("EvaluationParameters");
    const uint_t performanceLogFrequency = evaluationParameters.getParameter< uint_t >("performanceLogFrequency");
+   const uint_t evaluationFrequency     = evaluationParameters.getParameter< uint_t >("evaluationFrequency");
 
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(performanceLogFrequency);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(evaluationFrequency);
 
    // create non-uniform block forest (non-uniformity required for load balancing)
    const std::shared_ptr< StructuredBlockForest > blockForest =
@@ -327,6 +330,14 @@ int main(int argc, char** argv)
       loadBalancingFrequency, printLoadBalancingStatistics);
    timeloop.addFuncAfterTimeStep(loadBalancer, "Sweep: load balancing");
 
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
    // add VTK output
    addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
       blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID,
@@ -348,11 +359,13 @@ int main(int argc, char** argv)
 
    for (uint_t t = uint_c(0); t != timesteps; ++t)
    {
-      if (t % uint_c(real_c(timesteps / 100)) == uint_c(0))
+      timeloop.singleStep(timingPool, true);
+
+      if (t % evaluationFrequency == uint_c(0))
       {
-         WALBERLA_LOG_DEVEL_ON_ROOT("Performing timestep = " << t);
+         WALBERLA_LOG_DEVEL_ON_ROOT("time step = " << t << "\n\t\ttotal mass = " << *totalMass
+                                                   << "\n\t\texcess mass = " << *excessMass);
       }
-      timeloop.singleStep(timingPool, true);
 
       if (t % performanceLogFrequency == uint_c(0) && t > uint_c(0)) { timingPool.logResultOnRoot(); }
    }
diff --git a/apps/showcases/FreeSurface/BubblyPoiseuille.prm b/apps/showcases/FreeSurface/BubblyPoiseuille.prm
index e4fb01b3d..5b3c7728f 100644
--- a/apps/showcases/FreeSurface/BubblyPoiseuille.prm
+++ b/apps/showcases/FreeSurface/BubblyPoiseuille.prm
@@ -39,6 +39,7 @@ ModelParameters
 
 EvaluationParameters
 {
+   evaluationFrequency 5000;
    performanceLogFrequency 5000;
 }
 
diff --git a/apps/showcases/FreeSurface/CMakeLists.txt b/apps/showcases/FreeSurface/CMakeLists.txt
index b0dd58608..34bed9ecd 100644
--- a/apps/showcases/FreeSurface/CMakeLists.txt
+++ b/apps/showcases/FreeSurface/CMakeLists.txt
@@ -39,14 +39,14 @@ if( WALBERLA_BUILD_WITH_CODEGEN )
                                    GravityWaveLatticeModelGeneration)
 endif()
 
+waLBerla_add_executable(NAME    MovingDrop
+                        FILES   MovingDrop.cpp
+                        DEPENDS blockforest boundary core domain_decomposition field lbm postprocessing timeloop vtk)
+
 waLBerla_add_executable(NAME    RisingBubble
                         FILES   RisingBubble.cpp
                         DEPENDS blockforest boundary core domain_decomposition field lbm postprocessing timeloop vtk)
 
 waLBerla_add_executable(NAME    TaylorBubble
                         FILES   TaylorBubble.cpp
-                        DEPENDS blockforest boundary core domain_decomposition field lbm postprocessing timeloop vtk)
-
-waLBerla_add_executable(NAME    ZalesakDisk
-                        FILES   ZalesakDisk.cpp
                         DEPENDS blockforest boundary core domain_decomposition field lbm postprocessing timeloop vtk)
\ No newline at end of file
diff --git a/apps/showcases/FreeSurface/CapillaryWave.cpp b/apps/showcases/FreeSurface/CapillaryWave.cpp
index 64148c5b3..376a8d9cb 100644
--- a/apps/showcases/FreeSurface/CapillaryWave.cpp
+++ b/apps/showcases/FreeSurface/CapillaryWave.cpp
@@ -391,6 +391,14 @@ int main(int argc, char** argv)
 
    dynamicsHandler.addSweeps(timeloop);
 
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
    // add load balancing
    LoadBalancer< FlagField_T, CommunicationStencil_T, LatticeModelStencil_T > loadBalancer(
       blockForest, communication, pdfCommunication, bubbleModel, uint_c(50), uint_c(10), uint_c(5),
@@ -436,9 +444,10 @@ int main(int argc, char** argv)
          const std::vector< real_t > resultVector{ tNonDimensional, positionNonDimensional };
          if (t % evaluationFrequency == uint_c(0))
          {
-            WALBERLA_LOG_DEVEL("time step = " << t);
-            WALBERLA_LOG_DEVEL("\t\ttNonDimensional = " << tNonDimensional
-                                                        << "\n\t\tpositionNonDimensional = " << positionNonDimensional);
+            WALBERLA_LOG_DEVEL("time step = " << t << "\n\t\ttNonDimensional = " << tNonDimensional
+                                              << "\n\t\tpositionNonDimensional = " << positionNonDimensional
+                                              << "\n\t\ttotal mass = " << *totalMass
+                                              << "\n\t\texcess mass = " << *excessMass);
             writeVectorToFile(resultVector, filename);
          }
       }
diff --git a/apps/showcases/FreeSurface/DamBreakCylindrical.cpp b/apps/showcases/FreeSurface/DamBreakCylindrical.cpp
index f48e23232..0cf759466 100644
--- a/apps/showcases/FreeSurface/DamBreakCylindrical.cpp
+++ b/apps/showcases/FreeSurface/DamBreakCylindrical.cpp
@@ -32,6 +32,7 @@
 #include "lbm/field/AddToStorage.h"
 #include "lbm/free_surface/LoadBalancing.h"
 #include "lbm/free_surface/SurfaceMeshWriter.h"
+#include "lbm/free_surface/TotalMassComputer.h"
 #include "lbm/free_surface/VtkWriter.h"
 #include "lbm/free_surface/bubble_model/Geometry.h"
 #include "lbm/free_surface/dynamics/SurfaceDynamicsHandler.h"
@@ -527,6 +528,14 @@ int main(int argc, char** argv)
       evaluationFrequency, columnRadiusSample);
    timeloop.addFuncAfterTimeStep(columnRadiusEvaluator, "Evaluator: radius");
 
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
    // add VTK output
    addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
       blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID,
@@ -571,13 +580,14 @@ int main(int argc, char** argv)
             H              = real_c(*currentColumnHeight) / columnHeight;
          }
 
-         WALBERLA_LOG_DEVEL_ON_ROOT("time step =" << t);
-         WALBERLA_LOG_DEVEL_ON_ROOT("\t\tT = " << T << "\n\t\tZ_mean = " << Z_mean << "\n\t\tZ_max = " << Z_max
-                                               << "\n\t\tZ_min = " << Z_min
-                                               << "\n\t\tZ_stdDeviation = " << Z_stdDeviation << "\n\t\tH = " << H);
-
          WALBERLA_ROOT_SECTION()
          {
+            WALBERLA_LOG_DEVEL("time step =" << t);
+            WALBERLA_LOG_DEVEL("\t\tT = " << T << "\n\t\tZ_mean = " << Z_mean << "\n\t\tZ_max = " << Z_max
+                                          << "\n\t\tZ_min = " << Z_min << "\n\t\tZ_stdDeviation = " << Z_stdDeviation
+                                          << "\n\t\tH = " << H << "\n\t\ttotal mass = " << *totalMass
+                                          << "\n\t\texcess mass = " << *excessMass);
+
             const std::vector< real_t > resultVector{ T, Z_mean, Z_max, Z_min, Z_stdDeviation, H };
             writeNumberVector(resultVector, t, filename);
          }
diff --git a/apps/showcases/FreeSurface/DamBreakRectangular.cpp b/apps/showcases/FreeSurface/DamBreakRectangular.cpp
index e7abd62e6..fba4e9579 100644
--- a/apps/showcases/FreeSurface/DamBreakRectangular.cpp
+++ b/apps/showcases/FreeSurface/DamBreakRectangular.cpp
@@ -512,6 +512,14 @@ int main(int argc, char** argv)
       blockForest, freeSurfaceBoundaryHandling, domainSize, evaluationFrequency, currentColumnWidth);
    timeloop.addFuncAfterTimeStep(widthEvaluator, "Evaluator: column width");
 
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
    // add VTK output
    addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
       blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID,
@@ -543,9 +551,14 @@ int main(int argc, char** argv)
          const real_t H = real_c(*currentColumnHeight) / columnHeight;
          const std::vector< real_t > resultVector{ T, Z, H };
 
-         WALBERLA_LOG_DEVEL_ON_ROOT("time step =" << t);
-         WALBERLA_LOG_DEVEL_ON_ROOT("\t\tT = " << T << "\n\t\tZ = " << Z << "\n\t\tH = " << H);
-         WALBERLA_ROOT_SECTION() { writeNumberVector(resultVector, t, filename); }
+         WALBERLA_ROOT_SECTION()
+         {
+            WALBERLA_LOG_DEVEL("time step =" << t);
+            WALBERLA_LOG_DEVEL("\t\tT = " << T << "\n\t\tZ = " << Z << "\n\t\tH = " << H << "\n\t\ttotal mass = "
+                                          << *totalMass << "\n\t\texcess mass = " << *excessMass);
+
+            writeNumberVector(resultVector, t, filename);
+         }
 
          // simulation is considered converged
          if (Z >= real_c(domainSize[0]) / columnWidth - real_c(0.5))
diff --git a/apps/showcases/FreeSurface/DropImpact.cpp b/apps/showcases/FreeSurface/DropImpact.cpp
index 896c5b098..61da9474b 100644
--- a/apps/showcases/FreeSurface/DropImpact.cpp
+++ b/apps/showcases/FreeSurface/DropImpact.cpp
@@ -29,6 +29,7 @@
 #include "lbm/field/AddToStorage.h"
 #include "lbm/free_surface/LoadBalancing.h"
 #include "lbm/free_surface/SurfaceMeshWriter.h"
+#include "lbm/free_surface/TotalMassComputer.h"
 #include "lbm/free_surface/VtkWriter.h"
 #include "lbm/free_surface/bubble_model/Geometry.h"
 #include "lbm/free_surface/dynamics/SurfaceDynamicsHandler.h"
@@ -202,8 +203,10 @@ int main(int argc, char** argv)
    // read evaluation parameters from parameter file
    const auto evaluationParameters      = walberlaEnv.config()->getOneBlock("EvaluationParameters");
    const uint_t performanceLogFrequency = evaluationParameters.getParameter< uint_t >("performanceLogFrequency");
+   const uint_t evaluationFrequency     = evaluationParameters.getParameter< uint_t >("evaluationFrequency");
 
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(performanceLogFrequency);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(evaluationFrequency);
 
    // create non-uniform block forest (non-uniformity required for load balancing)
    const std::shared_ptr< StructuredBlockForest > blockForest =
@@ -325,6 +328,14 @@ int main(int argc, char** argv)
       loadBalancingFrequency, printLoadBalancingStatistics);
    timeloop.addFuncAfterTimeStep(loadBalancer, "Sweep: load balancing");
 
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
    // add VTK output
    addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
       blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID,
@@ -346,11 +357,13 @@ int main(int argc, char** argv)
 
    for (uint_t t = uint_c(0); t != timesteps; ++t)
    {
-      if (t % uint_c(real_c(timesteps / 100)) == uint_c(0))
+      timeloop.singleStep(timingPool, true);
+
+      if (t % evaluationFrequency == uint_c(0))
       {
-         WALBERLA_LOG_DEVEL_ON_ROOT("Performing timestep = " << t);
+         WALBERLA_LOG_DEVEL_ON_ROOT("time step = " << t << "\n\t\ttotal mass = " << *totalMass
+                                                   << "\n\t\texcess mass = " << *excessMass);
       }
-      timeloop.singleStep(timingPool, true);
 
       if (t % performanceLogFrequency == uint_c(0) && t > uint_c(0)) { timingPool.logResultOnRoot(); }
    }
diff --git a/apps/showcases/FreeSurface/DropImpact.prm b/apps/showcases/FreeSurface/DropImpact.prm
index 8437db98e..1790dffb4 100644
--- a/apps/showcases/FreeSurface/DropImpact.prm
+++ b/apps/showcases/FreeSurface/DropImpact.prm
@@ -41,6 +41,7 @@ ModelParameters
 
 EvaluationParameters
 {
+   evaluationFrequency 1000;
    performanceLogFrequency 3000;
 }
 
diff --git a/apps/showcases/FreeSurface/DropWetting.cpp b/apps/showcases/FreeSurface/DropWetting.cpp
index bdba59086..a55ad8808 100644
--- a/apps/showcases/FreeSurface/DropWetting.cpp
+++ b/apps/showcases/FreeSurface/DropWetting.cpp
@@ -29,6 +29,7 @@
 #include "lbm/field/AddToStorage.h"
 #include "lbm/free_surface/LoadBalancing.h"
 #include "lbm/free_surface/SurfaceMeshWriter.h"
+#include "lbm/free_surface/TotalMassComputer.h"
 #include "lbm/free_surface/VtkWriter.h"
 #include "lbm/free_surface/bubble_model/Geometry.h"
 #include "lbm/free_surface/dynamics/SurfaceDynamicsHandler.h"
@@ -368,6 +369,14 @@ int main(int argc, char** argv)
       blockForest, freeSurfaceBoundaryHandling, fillFieldID, dropHeight, evaluationFrequency);
    timeloop.addFuncAfterTimeStep(dropHeightEvaluator, "Evaluator: drop height");
 
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
    // add VTK output
    addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
       blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID,
@@ -395,8 +404,7 @@ int main(int argc, char** argv)
       // check convergence
       if (t % evaluationFrequency == uint_c(0))
       {
-         WALBERLA_LOG_DEVEL_ON_ROOT("time step = " << t)
-         WALBERLA_LOG_DEVEL_ON_ROOT("\t\tdrop height = " << *dropHeight)
+         WALBERLA_LOG_DEVEL_ON_ROOT("time step = " << t << "\n\t\tdrop height = " << *dropHeight)
          if (std::abs(formerDropHeight - *dropHeight) / *dropHeight < convergenceThreshold)
          {
             WALBERLA_LOG_DEVEL_ON_ROOT("Final converged drop height=" << *dropHeight);
diff --git a/apps/showcases/FreeSurface/GravityWave.cpp b/apps/showcases/FreeSurface/GravityWave.cpp
index acca70e45..702682bcf 100644
--- a/apps/showcases/FreeSurface/GravityWave.cpp
+++ b/apps/showcases/FreeSurface/GravityWave.cpp
@@ -554,6 +554,14 @@ int main(int argc, char** argv)
                                                                evaluationFrequency, symmetryNorm);
    timeloop.addFuncAfterTimeStep(symmetryEvaluator, "Evaluator: symmetry norm");
 
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
    // add VTK output
    addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
       blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID,
@@ -586,10 +594,11 @@ int main(int argc, char** argv)
          const std::vector< real_t > resultVector{ tNonDimensional, positionNonDimensional, *symmetryNorm };
          if (t % evaluationFrequency == uint_c(0))
          {
-            WALBERLA_LOG_DEVEL("time step = " << t);
-            WALBERLA_LOG_DEVEL("\t\ttNonDimensional = " << tNonDimensional
-                                                        << "\n\t\tpositionNonDimensional = " << positionNonDimensional
-                                                        << "\n\t\tsymmetryNorm = " << *symmetryNorm);
+            WALBERLA_LOG_DEVEL("time step = " << t << "\n\t\ttNonDimensional = " << tNonDimensional
+                                              << "\n\t\tpositionNonDimensional = " << positionNonDimensional
+                                              << "\n\t\tsymmetryNorm = " << *symmetryNorm << "\n\t\ttotal mass = "
+                                              << *totalMass << "\n\t\texcess mass = " << *excessMass);
+
             writeVectorToFile(resultVector, filename);
          }
       }
diff --git a/apps/showcases/FreeSurface/GravityWaveCodegen.cpp b/apps/showcases/FreeSurface/GravityWaveCodegen.cpp
index dd603d62c..65d18594b 100644
--- a/apps/showcases/FreeSurface/GravityWaveCodegen.cpp
+++ b/apps/showcases/FreeSurface/GravityWaveCodegen.cpp
@@ -516,6 +516,14 @@ int main(int argc, char** argv)
                                                                evaluationFrequency, symmetryNorm);
    timeloop.addFuncAfterTimeStep(symmetryEvaluator, "Evaluator: symmetry norm");
 
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
    // add VTK output
    addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T,
                  true, VectorFieldFlattened_T >(
@@ -549,10 +557,11 @@ int main(int argc, char** argv)
          const std::vector< real_t > resultVector{ tNonDimensional, positionNonDimensional, *symmetryNorm };
          if (t % evaluationFrequency == uint_c(0))
          {
-            WALBERLA_LOG_DEVEL("time step = " << t);
-            WALBERLA_LOG_DEVEL("\t\ttNonDimensional = " << tNonDimensional
-                                                        << "\n\t\tpositionNonDimensional = " << positionNonDimensional
-                                                        << "\n\t\tsymmetryNorm = " << *symmetryNorm);
+            WALBERLA_LOG_DEVEL("time step = " << t << "\n\t\ttNonDimensional = " << tNonDimensional
+                                              << "\n\t\tpositionNonDimensional = " << positionNonDimensional
+                                              << "\n\t\tsymmetryNorm = " << *symmetryNorm << "\n\t\ttotal mass = "
+                                              << *totalMass << "\n\t\texcess mass = " << *excessMass);
+
             writeVectorToFile(resultVector, filename);
          }
       }
diff --git a/apps/showcases/FreeSurface/MovingDrop.cpp b/apps/showcases/FreeSurface/MovingDrop.cpp
index 352a85c72..4fb4c49d3 100644
--- a/apps/showcases/FreeSurface/MovingDrop.cpp
+++ b/apps/showcases/FreeSurface/MovingDrop.cpp
@@ -27,6 +27,7 @@
 #include "lbm/field/AddToStorage.h"
 #include "lbm/free_surface/LoadBalancing.h"
 #include "lbm/free_surface/SurfaceMeshWriter.h"
+#include "lbm/free_surface/TotalMassComputer.h"
 #include "lbm/free_surface/VtkWriter.h"
 #include "lbm/free_surface/bubble_model/Geometry.h"
 #include "lbm/free_surface/dynamics/SurfaceDynamicsHandler.h"
@@ -189,8 +190,10 @@ int main(int argc, char** argv)
    // read evaluation parameters from parameter file
    const auto evaluationParameters      = walberlaEnv.config()->getOneBlock("EvaluationParameters");
    const uint_t performanceLogFrequency = evaluationParameters.getParameter< uint_t >("performanceLogFrequency");
+   const uint_t evaluationFrequency     = evaluationParameters.getParameter< uint_t >("evaluationFrequency");
 
    WALBERLA_LOG_DEVEL_VAR_ON_ROOT(performanceLogFrequency);
+   WALBERLA_LOG_DEVEL_VAR_ON_ROOT(evaluationFrequency);
 
    // create non-uniform block forest (non-uniformity required for load balancing)
    const std::shared_ptr< StructuredBlockForest > blockForest =
@@ -286,6 +289,14 @@ int main(int argc, char** argv)
       loadBalancingFrequency, printLoadBalancingStatistics);
    timeloop.addFuncAfterTimeStep(loadBalancer, "Sweep: load balancing");
 
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
+   const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
+   timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
+
    // add VTK output
    addVTKOutput< LatticeModel_T, FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T, VectorField_T >(
       blockForest, timeloop, walberlaEnv.config(), flagInfo, pdfFieldID, flagFieldID, fillFieldID, forceDensityFieldID,
@@ -307,9 +318,14 @@ int main(int argc, char** argv)
 
    for (uint_t t = uint_c(0); t != timesteps; ++t)
    {
-      if (t % uint_c(100) == uint_c(0)) { WALBERLA_LOG_DEVEL_ON_ROOT("Performing timestep=" << t); }
       timeloop.singleStep(timingPool, true);
 
+      if (t % evaluationFrequency == uint_c(0))
+      {
+         WALBERLA_LOG_DEVEL_ON_ROOT("time step = " << t << "\n\t\ttotal mass = " << *totalMass
+                                                   << "\n\t\texcess mass = " << *excessMass);
+      }
+
       if (t % performanceLogFrequency == uint_c(0) && t > uint_c(0)) { timingPool.logResultOnRoot(); }
    }
 
diff --git a/apps/showcases/FreeSurface/MovingDrop.prm b/apps/showcases/FreeSurface/MovingDrop.prm
index dceed84e7..79718c8c0 100644
--- a/apps/showcases/FreeSurface/MovingDrop.prm
+++ b/apps/showcases/FreeSurface/MovingDrop.prm
@@ -1,17 +1,17 @@
 BlockForestParameters
 {
-   cellsPerBlock                 < 10, 10, 10 >;
+   cellsPerBlock                 < 20, 20, 20 >;
    periodicity                   < 1, 1, 1 >;
    loadBalancingFrequency        0;
-   printLoadBalancingStatistics  true;
+   printLoadBalancingStatistics  false;
 }
 
 DomainParameters
 {
-   dropDiameter      50;
+   dropDiameter      20;
    dropCenterFactor  < 1, 1, 1 >;    // values multiplied with dropDiameter
-   poolHeightFactor  0;                // value multiplied with dropDiameter
-   domainSizeFactor  < 2, 2, 4 >;   // values multiplied with dropDiameter
+   poolHeightFactor  0;              // value multiplied with dropDiameter
+   domainSizeFactor  < 2, 2, 4 >;    // values multiplied with dropDiameter
 }
 
 PhysicsParameters
@@ -31,14 +31,16 @@ ModelParameters
    excessMassDistributionModel   EvenlyAllInterface;
    curvatureModel                FiniteDifferenceMethod;
    useSimpleMassExchange         false;
-   enableBubbleModel             false;
-   enableBubbleSplits            false; // only used if enableBubbleModel=true
    cellConversionThreshold       1e-2;
    cellConversionForceThreshold  1e-1;
+
+   enableBubbleModel             false;
+   enableBubbleSplits            false; // only used if enableBubbleModel=true
 }
 
 EvaluationParameters
 {
+   evaluationFrequency 100;
    performanceLogFrequency 10000;
 }
 
@@ -59,7 +61,7 @@ BoundaryParameters
 
 MeshOutputParameters
 {
-   writeFrequency 10000;
+   writeFrequency 1000;
    baseFolder mesh-out;
 }
 
@@ -67,7 +69,7 @@ VTK
 {
    fluid_field
    {
-      writeFrequency 10000;
+      writeFrequency 1000;
       ghostLayers 0;
       baseFolder vtk-out;
       samplingResolution 1;
diff --git a/apps/showcases/FreeSurface/RisingBubble.cpp b/apps/showcases/FreeSurface/RisingBubble.cpp
index 8fc773876..6699fdf1e 100644
--- a/apps/showcases/FreeSurface/RisingBubble.cpp
+++ b/apps/showcases/FreeSurface/RisingBubble.cpp
@@ -376,10 +376,12 @@ int main(int argc, char** argv)
       blockForest, freeSurfaceBoundaryHandling, evaluationFrequency, centerOfMass);
    timeloop.addFuncAfterTimeStep(centerOfMassComputer, "Evaluator: center of mass");
 
-   // add computation of total mass
-   const std::shared_ptr< real_t > totalMass = std::make_shared< real_t >(real_c(0));
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
    const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
-      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, evaluationFrequency, totalMass);
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
    timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
 
    // add VTK output
@@ -419,10 +421,10 @@ int main(int argc, char** argv)
             const real_t dragForce = real_c(4) / real_c(3) * gravitationalAccelerationZ * real_c(bubbleDiameter) /
                                      (riseVelocity * riseVelocity);
 
-            WALBERLA_LOG_DEVEL("time step = " << t);
-            WALBERLA_LOG_DEVEL("\t\tcenterOfMass = " << *centerOfMass << "\n\t\triseVelocity = " << riseVelocity
-                                                     << "\n\t\tdragForce = " << dragForce);
-            WALBERLA_LOG_DEVEL("\t\ttotalMass = " << *totalMass);
+            WALBERLA_LOG_DEVEL("time step = " << t << "\n\t\tcenterOfMass = " << *centerOfMass
+                                              << "\n\t\triseVelocity = " << riseVelocity
+                                              << "\n\t\tdragForce = " << dragForce << "\n\t\ttotalMass = " << *totalMass
+                                              << "\n\t\texcessMass = " << *excessMass);
 
             const std::vector< real_t > resultVector{ (*centerOfMass)[2], riseVelocity, dragForce };
 
diff --git a/apps/showcases/FreeSurface/TaylorBubble.cpp b/apps/showcases/FreeSurface/TaylorBubble.cpp
index a045cd6ac..ce2a45302 100644
--- a/apps/showcases/FreeSurface/TaylorBubble.cpp
+++ b/apps/showcases/FreeSurface/TaylorBubble.cpp
@@ -405,9 +405,12 @@ int main(int argc, char** argv)
       blockForest, freeSurfaceBoundaryHandling, evaluationFrequency, centerOfMass);
    timeloop.addFuncAfterTimeStep(centerOfMassComputer, "Evaluator: center of mass");
 
-   const std::shared_ptr< real_t > totalMass = std::make_shared< real_t >(real_c(0));
+   // add evaluator for total and excessive mass (mass that is currently undistributed)
+   const std::shared_ptr< real_t > totalMass  = std::make_shared< real_t >(real_c(0));
+   const std::shared_ptr< real_t > excessMass = std::make_shared< real_t >(real_c(0));
    const TotalMassComputer< FreeSurfaceBoundaryHandling_T, PdfField_T, FlagField_T, ScalarField_T > totalMassComputer(
-      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, evaluationFrequency, totalMass);
+      blockForest, freeSurfaceBoundaryHandling, pdfFieldID, fillFieldID, dynamicsHandler.getConstExcessMassFieldID(),
+      evaluationFrequency, totalMass, excessMass);
    timeloop.addFuncAfterTimeStep(totalMassComputer, "Evaluator: total mass");
 
    // add VTK output
@@ -447,20 +450,20 @@ int main(int argc, char** argv)
             const real_t dragForce = real_c(4) / real_c(3) * gravitationalAccelerationZ * real_c(bubbleDiameter) /
                                      (riseVelocity * riseVelocity);
 
-            WALBERLA_LOG_DEVEL("time step = " << t);
-            WALBERLA_LOG_DEVEL("\t\tcenterOfMass = " << *centerOfMass << "\n\t\triseVelocity = " << riseVelocity
-                                                     << "\n\t\tdragForce = " << dragForce);
-            WALBERLA_LOG_DEVEL("\t\ttotalMass = " << *totalMass);
+            WALBERLA_LOG_DEVEL("time step = " << t << "\n\t\tcenterOfMass = " << *centerOfMass
+                                              << "\n\t\triseVelocity = " << riseVelocity
+                                              << "\n\t\tdragForce = " << dragForce << "\n\t\ttotalMass = " << *totalMass
+                                              << "\n\t\texcessMass = " << *excessMass);
 
             const std::vector< real_t > resultVector{ (*centerOfMass)[2], riseVelocity, dragForce };
 
             writeVectorToFile(resultVector, t, filename);
          }
-
-         timestepOld     = t;
-         centerOfMassOld = *centerOfMass;
       }
 
+      timestepOld     = t;
+      centerOfMassOld = *centerOfMass;
+
       // stop simulation before bubble hits the top wall
       if ((*centerOfMass)[2] > stoppingHeight) { break; }
 
diff --git a/src/lbm/free_surface/TotalMassComputer.h b/src/lbm/free_surface/TotalMassComputer.h
index 192ec8755..7b3fda3e8 100644
--- a/src/lbm/free_surface/TotalMassComputer.h
+++ b/src/lbm/free_surface/TotalMassComputer.h
@@ -50,10 +50,10 @@ class TotalMassComputer
                      const std::weak_ptr< const FreeSurfaceBoundaryHandling_T >& freeSurfaceBoundaryHandling,
                      const ConstBlockDataID& pdfFieldID, const ConstBlockDataID& fillFieldID,
                      const ConstBlockDataID& excessMassFieldID, uint_t frequency,
-                     const std::shared_ptr< real_t >& totalMass)
+                     const std::shared_ptr< real_t >& totalMass, const std::shared_ptr< real_t >& excessMass)
       : blockForest_(blockForest), freeSurfaceBoundaryHandling_(freeSurfaceBoundaryHandling), pdfFieldID_(pdfFieldID),
-        fillFieldID_(fillFieldID), excessMassFieldID_(excessMassFieldID), totalMass_(totalMass), frequency_(frequency),
-        executionCounter_(uint_c(0))
+        fillFieldID_(fillFieldID), excessMassFieldID_(excessMassFieldID), totalMass_(totalMass),
+        excessMass_(excessMass), frequency_(frequency), executionCounter_(uint_c(0))
    {}
 
    void operator()()
@@ -66,11 +66,10 @@ class TotalMassComputer
       auto freeSurfaceBoundaryHandling = freeSurfaceBoundaryHandling_.lock();
       WALBERLA_CHECK_NOT_NULLPTR(freeSurfaceBoundaryHandling);
 
-      if (executionCounter_ == uint_c(0)) { computeMass(blockForest, freeSurfaceBoundaryHandling); }
-      else
+      // only evaluate in given frequencies
+      if (executionCounter_ % frequency_ == uint_c(0) || executionCounter_ == uint_c(0))
       {
-         // only evaluate in given frequencies
-         if (executionCounter_ % frequency_ == uint_c(0)) { computeMass(blockForest, freeSurfaceBoundaryHandling); }
+         computeMass(blockForest, freeSurfaceBoundaryHandling);
       }
 
       ++executionCounter_;
@@ -82,7 +81,8 @@ class TotalMassComputer
       const BlockDataID flagFieldID = freeSurfaceBoundaryHandling->getFlagFieldID();
       const typename FreeSurfaceBoundaryHandling_T::FlagInfo_T& flagInfo = freeSurfaceBoundaryHandling->getFlagInfo();
 
-      real_t mass = real_c(0);
+      real_t mass       = real_c(0);
+      real_t excessMass = real_c(0);
 
       for (auto blockIt = blockForest->begin(); blockIt != blockForest->end(); ++blockIt)
       {
@@ -104,6 +104,8 @@ class TotalMassComputer
                {
                   const real_t density = pdfField->getDensity(pdfFieldIt.cell());
                   mass += *fillFieldIt * density + *excessMassFieldIt;
+
+                  if (excessMass_ != nullptr) { excessMass += *excessMassFieldIt; }
                }
             }) // WALBERLA_FOR_ALL_CELLS_OMP
          }
@@ -122,8 +124,13 @@ class TotalMassComputer
       }
 
       mpi::allReduceInplace< real_t >(mass, mpi::SUM);
-
       *totalMass_ = mass;
+
+      if (excessMass_ != nullptr)
+      {
+         mpi::allReduceInplace< real_t >(excessMass, mpi::SUM);
+         *excessMass_ = excessMass;
+      }
    };
 
  private:
@@ -135,6 +142,7 @@ class TotalMassComputer
    const ConstBlockDataID excessMassFieldID_ = ConstBlockDataID();
 
    std::shared_ptr< real_t > totalMass_;
+   std::shared_ptr< real_t > excessMass_ = nullptr; // mass stored in the excessMassField
 
    uint_t frequency_;
    uint_t executionCounter_;
diff --git a/src/lbm/free_surface/VtkWriter.h b/src/lbm/free_surface/VtkWriter.h
index a27103510..726e0ac73 100644
--- a/src/lbm/free_surface/VtkWriter.h
+++ b/src/lbm/free_surface/VtkWriter.h
@@ -56,7 +56,7 @@ void addVTKOutput(const std::weak_ptr< StructuredBlockForest >& blockForestPtr,
                   const BlockDataID& forceDensityFieldID, const BlockDataID& curvatureFieldID,
                   const BlockDataID& normalFieldID, const BlockDataID& obstacleNormalFieldID)
 {
-   using value_type = typename FlagField_T::value_type;
+   using value_type       = typename FlagField_T::value_type;
    const auto blockForest = blockForestPtr.lock();
    WALBERLA_CHECK_NOT_NULLPTR(blockForest);
 
@@ -89,27 +89,34 @@ void addVTKOutput(const std::weak_ptr< StructuredBlockForest >& blockForestPtr,
          std::make_shared< VTKWriter< VectorField_T, float > >(obstacleNormalFieldID, "obstacle_normal"));
       if constexpr (useCodegen)
       {
-         writers.push_back(
-            std::make_shared< VTKWriter< VectorFieldFlattened_T, float > >(forceDensityFieldID, "force_density"));
+         if (forceDensityFieldID != BlockDataID())
+         {
+            writers.push_back(
+               std::make_shared< VTKWriter< VectorFieldFlattened_T, float > >(forceDensityFieldID, "force_density"));
+         }
       }
       else
       {
-         writers.push_back(std::make_shared< VTKWriter< VectorField_T, float > >(forceDensityFieldID, "force_density"));
+         if (forceDensityFieldID != BlockDataID())
+         {
+            writers.push_back(
+               std::make_shared< VTKWriter< VectorField_T, float > >(forceDensityFieldID, "force_density"));
+         }
       }
 
       // map flagIDs to integer values
       const auto flagMapper =
          std::make_shared< field::FlagFieldMapping< FlagField_T, value_type > >(flagFieldID, "mapped_flag");
-      flagMapper->addMapping(flagIDs::liquidFlagID, numeric_cast<value_type>(1));
-      flagMapper->addMapping(flagIDs::interfaceFlagID, numeric_cast<value_type>(2));
-      flagMapper->addMapping(flagIDs::gasFlagID, numeric_cast<value_type>(3));
-      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::noSlipFlagID, numeric_cast<value_type>(4));
-      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::freeSlipFlagID, numeric_cast<value_type>(6));
-      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::ubbFlagID, numeric_cast<value_type>(6));
-      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::ubbInflowFlagID, numeric_cast<value_type>(7));
-      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::pressureFlagID, numeric_cast<value_type>(8));
-      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::pressureOutflowFlagID, numeric_cast<value_type>(9));
-      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::outletFlagID, numeric_cast<value_type>(10));
+      flagMapper->addMapping(flagIDs::liquidFlagID, numeric_cast< value_type >(1));
+      flagMapper->addMapping(flagIDs::interfaceFlagID, numeric_cast< value_type >(2));
+      flagMapper->addMapping(flagIDs::gasFlagID, numeric_cast< value_type >(3));
+      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::noSlipFlagID, numeric_cast< value_type >(4));
+      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::freeSlipFlagID, numeric_cast< value_type >(6));
+      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::ubbFlagID, numeric_cast< value_type >(6));
+      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::ubbInflowFlagID, numeric_cast< value_type >(7));
+      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::pressureFlagID, numeric_cast< value_type >(8));
+      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::pressureOutflowFlagID, numeric_cast< value_type >(9));
+      flagMapper->addMapping(FreeSurfaceBoundaryHandling_T::outletFlagID, numeric_cast< value_type >(10));
 
       writers.push_back(flagMapper);
 
diff --git a/src/lbm/free_surface/dynamics/ExcessMassDistributionModel.h b/src/lbm/free_surface/dynamics/ExcessMassDistributionModel.h
index 2e4d0b798..a4b86c43c 100644
--- a/src/lbm/free_surface/dynamics/ExcessMassDistributionModel.h
+++ b/src/lbm/free_surface/dynamics/ExcessMassDistributionModel.h
@@ -63,15 +63,21 @@ namespace free_surface
  *       cells, i.e., cells that are non-newly converted to interface. Falls back to WeightedAllInterface if not
  * applicable.
  *
- *  - EvenlyLiquidAndAllInterface:
+ *  - EvenlyAllInterfaceAndLiquid:
  *      Excess mass is distributed evenly among all neighboring interface and liquid cells (see p.47 in master thesis of
  *      M. Lehmann, 2019). The excess mass distributed to liquid cells does neither modify the cell's density nor fill
  *      level. Instead, it is stored in an additional excess mass field. Therefore, not only the converted interface
  *      cells' excess mass is distributed, but also the excess mass of liquid cells stored in this additional field.
  *
- *  - EvenlyLiquidAndAllInterfacePreferInterface:
- *      Similar to EvenlyLiquidAndAllInterface, however, excess mass is preferably distributed to interface cells. It is
+ *  - EvenlyAllInterfaceFallbackLiquid:
+ *      Similar to EvenlyAllInterfaceAndLiquid, however, excess mass is preferably distributed to interface cells. It is
  *      distributed to liquid cells only if there are no neighboring interface cells available.
+ *
+ *  - EvenlyNewInterfaceFallbackLiquid:
+ *      Similar to EvenlyAllInterfaceFallbackLiquid, however, excess mass is preferably distributed to newly
+ *      converted interface cells. If there are no newly converted interface cells, the excess mass is distributed to
+ *      old interface cells. The excess mass is distributed to neighboring liquid cells only if there are no neighboring
+ *      interface cells available.
  * ********************************************************************************************************************/
 class ExcessMassDistributionModel
 {
@@ -83,8 +89,9 @@ class ExcessMassDistributionModel
       WeightedAllInterface,
       WeightedNewInterface,
       WeightedOldInterface,
-      EvenlyLiquidAndAllInterface,
-      EvenlyLiquidAndAllInterfacePreferInterface
+      EvenlyAllInterfaceAndLiquid,
+      EvenlyAllInterfaceFallbackLiquid,
+      EvenlyNewInterfaceFallbackLiquid
    };
 
    ExcessMassDistributionModel(const std::string& modelName) : modelName_(modelName), modelType_(chooseType(modelName))
@@ -107,9 +114,11 @@ class ExcessMassDistributionModel
          break;
       case ExcessMassModel::WeightedOldInterface:
          break;
-      case ExcessMassModel::EvenlyLiquidAndAllInterface:
+      case ExcessMassModel::EvenlyAllInterfaceAndLiquid:
+         break;
+      case ExcessMassModel::EvenlyAllInterfaceFallbackLiquid:
          break;
-      case ExcessMassModel::EvenlyLiquidAndAllInterfacePreferInterface:
+      case ExcessMassModel::EvenlyNewInterfaceFallbackLiquid:
          break;
       }
    }
@@ -130,10 +139,12 @@ class ExcessMassDistributionModel
              modelType_ == ExcessMassModel::WeightedNewInterface || modelType_ == ExcessMassModel::WeightedOldInterface;
    }
 
-   inline bool isEvenlyLiquidAndAllInterfacePreferInterfaceType() const
+   inline bool isEvenlyAllInterfaceFallbackLiquidType() const
    {
-      return modelType_ == ExcessMassModel::EvenlyLiquidAndAllInterface ||
-             modelType_ == ExcessMassModel::EvenlyLiquidAndAllInterfacePreferInterface;
+      return modelType_ == ExcessMassModel::EvenlyAllInterfaceAndLiquid ||
+             modelType_ == ExcessMassModel::EvenlyAllInterfaceFallbackLiquid ||
+             modelType_ == ExcessMassModel::EvenlyNewInterfaceFallbackLiquid;
+      ;
    }
 
    static inline std::initializer_list< const ExcessMassModel > getTypeIterator() { return listOfAllEnums; }
@@ -153,14 +164,19 @@ class ExcessMassDistributionModel
 
       if (!string_icompare(modelName, "WeightedOldInterface")) { return ExcessMassModel::WeightedOldInterface; }
 
-      if (!string_icompare(modelName, "EvenlyLiquidAndAllInterface"))
+      if (!string_icompare(modelName, "EvenlyAllInterfaceAndLiquid"))
+      {
+         return ExcessMassModel::EvenlyAllInterfaceAndLiquid;
+      }
+
+      if (!string_icompare(modelName, "EvenlyAllInterfaceFallbackLiquid"))
       {
-         return ExcessMassModel::EvenlyLiquidAndAllInterface;
+         return ExcessMassModel::EvenlyAllInterfaceFallbackLiquid;
       }
 
-      if (!string_icompare(modelName, "EvenlyLiquidAndAllInterfacePreferInterface"))
+      if (!string_icompare(modelName, "EvenlyNewInterfaceFallbackLiquid"))
       {
-         return ExcessMassModel::EvenlyLiquidAndAllInterfacePreferInterface;
+         return ExcessMassModel::EvenlyNewInterfaceFallbackLiquid;
       }
 
       WALBERLA_ABORT("The specified PDF reinitialization model " << modelName << " is not available.");
@@ -190,11 +206,14 @@ class ExcessMassDistributionModel
          modelName = "WeightedOldInterface";
          break;
 
-      case ExcessMassModel::EvenlyLiquidAndAllInterface:
-         modelName = "EvenlyLiquidAndAllInterface";
+      case ExcessMassModel::EvenlyAllInterfaceAndLiquid:
+         modelName = "EvenlyAllInterfaceAndLiquid";
+         break;
+      case ExcessMassModel::EvenlyAllInterfaceFallbackLiquid:
+         modelName = "EvenlyAllInterfaceFallbackLiquid";
          break;
-      case ExcessMassModel::EvenlyLiquidAndAllInterfacePreferInterface:
-         modelName = "EvenlyLiquidAndAllInterfacePreferInterface";
+      case ExcessMassModel::EvenlyNewInterfaceFallbackLiquid:
+         modelName = "EvenlyNewInterfaceFallbackLiquid";
          break;
       }
       return modelName;
@@ -203,10 +222,15 @@ class ExcessMassDistributionModel
    std::string modelName_;
    ExcessMassModel modelType_;
    static constexpr std::initializer_list< const ExcessMassModel > listOfAllEnums = {
-      ExcessMassModel::EvenlyAllInterface,          ExcessMassModel::EvenlyNewInterface,
-      ExcessMassModel::EvenlyOldInterface,          ExcessMassModel::WeightedAllInterface,
-      ExcessMassModel::WeightedNewInterface,        ExcessMassModel::WeightedOldInterface,
-      ExcessMassModel::EvenlyLiquidAndAllInterface, ExcessMassModel::EvenlyLiquidAndAllInterfacePreferInterface
+      ExcessMassModel::EvenlyAllInterface,
+      ExcessMassModel::EvenlyNewInterface,
+      ExcessMassModel::EvenlyOldInterface,
+      ExcessMassModel::WeightedAllInterface,
+      ExcessMassModel::WeightedNewInterface,
+      ExcessMassModel::WeightedOldInterface,
+      ExcessMassModel::EvenlyAllInterfaceAndLiquid,
+      ExcessMassModel::EvenlyAllInterfaceFallbackLiquid,
+      ExcessMassModel::EvenlyNewInterfaceFallbackLiquid
    };
 
 }; // class ExcessMassDistributionModel
diff --git a/src/lbm/free_surface/dynamics/ExcessMassDistributionSweep.h b/src/lbm/free_surface/dynamics/ExcessMassDistributionSweep.h
index ab9efe397..34ebe1b20 100644
--- a/src/lbm/free_surface/dynamics/ExcessMassDistributionSweep.h
+++ b/src/lbm/free_surface/dynamics/ExcessMassDistributionSweep.h
@@ -71,9 +71,8 @@ class ExcessMassDistributionSweepBase
    /********************************************************************************************************************
     * Determines the number of a cell's neighboring liquid and interface cells.
     *******************************************************************************************************************/
-   void getNumberOfEvenlyLiquidAndAllInterfacePreferInterfaceNeighbors(const FlagField_T* flagField, const Cell& cell,
-                                                                       uint_t& liquidNeighbors,
-                                                                       uint_t& interfaceNeighbors);
+   void getNumberOfLiquidAndInterfaceNeighbors(const FlagField_T* flagField, const Cell& cell, uint_t& liquidNeighbors,
+                                               uint_t& interfaceNeighbors, uint_t& newInterfaceNeighbors);
 
    ExcessMassDistributionModel excessMassDistributionModel_;
    BlockDataID fillFieldID_;
@@ -171,6 +170,8 @@ class ExcessMassDistributionSweepInterfaceWeighted
  * Distribute the excess mass evenly among
  *  - all neighboring liquid and interface cells (see p. 47 in master thesis of M. Lehmann, 2019)
  *  - all neighboring interface cells and only to liquid cells if there exists no neighboring interface cell
+ *  - new neighboring interface cells, if not available to old interface cells and only to liquid cells if there exists
+ *    no neighboring interface cell
  *
  * Neither the fill level, nor the density of liquid cells is modified. Instead, the excess mass is stored in an
  * additional field.
diff --git a/src/lbm/free_surface/dynamics/ExcessMassDistributionSweep.impl.h b/src/lbm/free_surface/dynamics/ExcessMassDistributionSweep.impl.h
index ba30c8445..12ffe0317 100644
--- a/src/lbm/free_surface/dynamics/ExcessMassDistributionSweep.impl.h
+++ b/src/lbm/free_surface/dynamics/ExcessMassDistributionSweep.impl.h
@@ -79,18 +79,23 @@ void ExcessMassDistributionSweepInterfaceEvenly< LatticeModel_T, FlagField_T, Sc
 
 template< typename LatticeModel_T, typename FlagField_T, typename ScalarField_T, typename VectorField_T >
 void ExcessMassDistributionSweepBase< LatticeModel_T, FlagField_T, ScalarField_T, VectorField_T >::
-   getNumberOfEvenlyLiquidAndAllInterfacePreferInterfaceNeighbors(const FlagField_T* flagField, const Cell& cell,
-                                                                  uint_t& liquidNeighbors, uint_t& interfaceNeighbors)
+   getNumberOfLiquidAndInterfaceNeighbors(const FlagField_T* flagField, const Cell& cell, uint_t& liquidNeighbors,
+                                          uint_t& interfaceNeighbors, uint_t& newInterfaceNeighbors)
 {
-   interfaceNeighbors = uint_c(0);
-   liquidNeighbors    = uint_c(0);
+   newInterfaceNeighbors = uint_c(0);
+   interfaceNeighbors    = uint_c(0);
+   liquidNeighbors       = uint_c(0);
 
    for (auto d = LatticeModel_T::Stencil::beginNoCenter(); d != LatticeModel_T::Stencil::end(); ++d)
    {
       const Cell neighborCell = Cell(cell.x() + d.cx(), cell.y() + d.cy(), cell.z() + d.cz());
       auto neighborFlags      = flagField->get(neighborCell);
 
-      if (isFlagSet(neighborFlags, flagInfo_.interfaceFlag)) { ++interfaceNeighbors; }
+      if (isFlagSet(neighborFlags, flagInfo_.interfaceFlag))
+      {
+         ++interfaceNeighbors;
+         if (isFlagSet(neighborFlags, flagInfo_.convertedFlag)) { ++newInterfaceNeighbors; }
+      }
       else
       {
          if (isFlagSet(neighborFlags, flagInfo_.liquidFlag)) { ++liquidNeighbors; }
@@ -147,7 +152,8 @@ void ExcessMassDistributionSweepInterfaceEvenly< LatticeModel_T, FlagField_T, Sc
 
    if (interfaceNeighbors == uint_c(0))
    {
-      WALBERLA_LOG_WARNING("No interface cell is in the neighborhood to distribute excess mass to. Mass is lost.");
+      WALBERLA_LOG_WARNING(
+         "No interface cell is in the neighborhood to distribute excess mass to. Mass is lost/gained.");
       return;
    }
 
@@ -285,7 +291,8 @@ void ExcessMassDistributionSweepInterfaceWeighted< LatticeModel_T, FlagField_T,
 
    if (interfaceNeighbors == uint_c(0))
    {
-      WALBERLA_LOG_WARNING("No interface cell is in the neighborhood to distribute excess mass to. Mass is lost.");
+      WALBERLA_LOG_WARNING(
+         "No interface cell is in the neighborhood to distribute excess mass to. Mass is lost/gained.");
       return;
    }
 
@@ -505,7 +512,9 @@ void ExcessMassDistributionSweepInterfaceAndLiquid< LatticeModel_T, FlagField_T,
             // calculate excess fill level
             const real_t excessFill = newGas ? fillField->get(cell) : (fillField->get(cell) - real_c(1.0));
 
-            // store excess mass such that it can be distributed below
+            // store excess mass such that it can be distributed below (no += here because cell was an interface cell
+            // that can not have an excess mass stored in the field; any excess mass is added to the interface cell's
+            // fill level)
             srcExcessMassField->get(cell) = excessFill * pdfField->getDensity(cell);
 
             if (newGas) { fillField->get(cell) = real_c(0.0); }
@@ -533,28 +542,41 @@ void ExcessMassDistributionSweepInterfaceAndLiquid< LatticeModel_T, FlagField_T,
    using Base_T = ExcessMassDistributionSweepBase_T;
 
    // get number of liquid and interface neighbors
-   uint_t liquidNeighbors    = uint_c(0);
-   uint_t interfaceNeighbors = uint_c(0);
-   Base_T::getNumberOfEvenlyLiquidAndAllInterfacePreferInterfaceNeighbors(flagField, cell, liquidNeighbors,
-                                                                          interfaceNeighbors);
-   const uint_t EvenlyLiquidAndAllInterfacePreferInterfaceNeighbors = liquidNeighbors + interfaceNeighbors;
+   uint_t liquidNeighbors       = uint_c(0);
+   uint_t interfaceNeighbors    = uint_c(0);
+   uint_t newInterfaceNeighbors = uint_c(0);
+   Base_T::getNumberOfLiquidAndInterfaceNeighbors(flagField, cell, liquidNeighbors, interfaceNeighbors,
+                                                  newInterfaceNeighbors);
+   const uint_t liquidAndInterfaceNeighbors = liquidNeighbors + interfaceNeighbors;
 
-   if (EvenlyLiquidAndAllInterfacePreferInterfaceNeighbors == uint_c(0))
+   if (liquidAndInterfaceNeighbors == uint_c(0))
    {
       WALBERLA_LOG_WARNING(
-         "No liquid or interface cell is in the neighborhood to distribute excess mass to. Mass is lost.");
+         "No liquid or interface cell is in the neighborhood to distribute excess mass to. Mass is lost/gained.");
       return;
    }
 
-   const bool preferInterface =
-      Base_T::excessMassDistributionModel_.getModelType() ==
-         ExcessMassDistributionModel::ExcessMassModel::EvenlyLiquidAndAllInterfacePreferInterface &&
-      interfaceNeighbors > uint_c(0);
+   // check if there are neighboring new interface cells
+   const bool preferNewInterface = Base_T::excessMassDistributionModel_.getModelType() ==
+                                      ExcessMassDistributionModel::ExcessMassModel::EvenlyNewInterfaceFallbackLiquid &&
+                                   newInterfaceNeighbors > uint_c(0);
+
+   // check if there are neighboring interface cells
+   const bool preferInterface = (Base_T::excessMassDistributionModel_.getModelType() ==
+                                    ExcessMassDistributionModel::ExcessMassModel::EvenlyAllInterfaceFallbackLiquid ||
+                                 !preferNewInterface) &&
+                                interfaceNeighbors > uint_c(0) &&
+                                Base_T::excessMassDistributionModel_.getModelType() !=
+                                   ExcessMassDistributionModel::ExcessMassModel::EvenlyAllInterfaceAndLiquid;
 
    // compute mass to be distributed to neighboring cells
    real_t deltaMass;
-   if (preferInterface) { deltaMass = excessMass / real_c(interfaceNeighbors); }
-   else { deltaMass = excessMass / real_c(EvenlyLiquidAndAllInterfacePreferInterfaceNeighbors); }
+   if (preferNewInterface) { deltaMass = excessMass / real_c(newInterfaceNeighbors); }
+   else
+   {
+      if (preferInterface) { deltaMass = excessMass / real_c(interfaceNeighbors); }
+      else { deltaMass = excessMass / real_c(liquidAndInterfaceNeighbors); }
+   }
 
    // distribute the excess mass
    for (auto pushDir = LatticeModel_T::Stencil::beginNoCenter(); pushDir != LatticeModel_T::Stencil::end(); ++pushDir)
@@ -571,20 +593,35 @@ void ExcessMassDistributionSweepInterfaceAndLiquid< LatticeModel_T, FlagField_T,
          continue;
       }
 
-      if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.interfaceFlag))
+      // distribute excess mass to newly converted interface cell
+      if (flagField->isMaskSet(neighborCell, Base_T::flagInfo_.convertedFlag | Base_T::flagInfo_.interfaceFlag))
       {
          // get density of neighboring interface cell
          const real_t neighborDensity = pdfField->getDensity(neighborCell);
 
-         // add excess mass directly to fill level for neighboring interface cells
+         // add excess mass directly to fill level for newly converted neighboring interface cells
          fillField->get(neighborCell) += deltaMass / neighborDensity;
       }
       else
       {
-         if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.liquidFlag) && !preferInterface)
+         // distribute excess mass to old interface cell
+         if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.interfaceFlag) && !preferNewInterface)
          {
-            // add excess mass to excessMassField for neighboring liquid cells
-            dstExcessMassField->get(neighborCell) += deltaMass;
+            // get density of neighboring interface cell
+            const real_t neighborDensity = pdfField->getDensity(neighborCell);
+
+            // add excess mass directly to fill level for neighboring interface cells
+            fillField->get(neighborCell) += deltaMass / neighborDensity;
+         }
+         else
+         {
+            // distribute excess mass to liquid cell
+            if (flagField->isFlagSet(neighborCell, Base_T::flagInfo_.liquidFlag) && !preferInterface &&
+                !preferNewInterface)
+            {
+               // add excess mass to excessMassField for neighboring liquid cells
+               dstExcessMassField->get(neighborCell) += deltaMass;
+            }
          }
       }
    }
diff --git a/src/lbm/free_surface/dynamics/SurfaceDynamicsHandler.h b/src/lbm/free_surface/dynamics/SurfaceDynamicsHandler.h
index d65469940..7180ec57c 100644
--- a/src/lbm/free_surface/dynamics/SurfaceDynamicsHandler.h
+++ b/src/lbm/free_surface/dynamics/SurfaceDynamicsHandler.h
@@ -103,7 +103,7 @@ class SurfaceDynamicsHandler
                         "generate the Smagorinsky model directly into the kernel.");
       }
 
-      if (excessMassDistributionModel_.isEvenlyLiquidAndAllInterfacePreferInterfaceType())
+      if (excessMassDistributionModel_.isEvenlyAllInterfaceFallbackLiquidType())
       {
          // add additional field for storing excess mass in liquid cells
          excessMassFieldID_ =
@@ -389,14 +389,16 @@ class SurfaceDynamicsHandler
          }
          else
          {
-            if (excessMassDistributionModel_.isEvenlyLiquidAndAllInterfacePreferInterfaceType())
+            if (excessMassDistributionModel_.isEvenlyAllInterfaceFallbackLiquidType())
             {
                const ExcessMassDistributionSweepInterfaceAndLiquid< LatticeModel_T, FlagField_T, ScalarField_T,
                                                                     VectorField_T >
                   distributeMassSweep(excessMassDistributionModel_, fillFieldID_, flagFieldID_, pdfFieldID_, flagInfo,
                                       excessMassFieldID_);
                timeloop.add()
+                  // perform this sweep also on "onlyLBM" blocks because liquid cells also exchange excess mass here
                   << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::fullFreeSurface)
+                  << Sweep(distributeMassSweep, "Sweep: excess mass distribution", StateSweep::onlyLBM)
                   << Sweep(emptySweep, "Empty sweep: distribute excess mass")
                   << AfterFunction(CommunicationCorner_T(blockForest_, fillFieldID_, excessMassFieldID_),
                                    "Communication: after excess mass distribution sweep")
diff --git a/tests/lbm/free_surface/dynamics/ExcessMassDistributionParallelTest.cpp b/tests/lbm/free_surface/dynamics/ExcessMassDistributionParallelTest.cpp
index 8c99cf872..3bda9fd57 100644
--- a/tests/lbm/free_surface/dynamics/ExcessMassDistributionParallelTest.cpp
+++ b/tests/lbm/free_surface/dynamics/ExcessMassDistributionParallelTest.cpp
@@ -275,7 +275,7 @@ void runSimulation(const ExcessMassDistributionModel& excessMassDistributionMode
       }
       else
       {
-         if (excessMassDistributionModel.isEvenlyLiquidAndAllInterfacePreferInterfaceType())
+         if (excessMassDistributionModel.isEvenlyAllInterfaceFallbackLiquidType())
          {
             const ExcessMassDistributionSweepInterfaceAndLiquid< LatticeModel_T, FlagField_T, ScalarField_T,
                                                                  VectorField_T >
@@ -876,7 +876,7 @@ void runSimulation(const ExcessMassDistributionModel& excessMassDistributionMode
          }
 
          if (excessMassDistributionModel.getModelType() ==
-             ExcessMassDistributionModel::ExcessMassModel::EvenlyLiquidAndAllInterface)
+             ExcessMassDistributionModel::ExcessMassModel::EvenlyAllInterfaceAndLiquid)
          {
             // left block
             if (globalCell == Cell(cell_idx_c(0), cell_idx_c(0), cell_idx_c(0)))
@@ -990,7 +990,7 @@ void runSimulation(const ExcessMassDistributionModel& excessMassDistributionMode
          }
 
          if (excessMassDistributionModel.getModelType() ==
-             ExcessMassDistributionModel::ExcessMassModel::EvenlyLiquidAndAllInterfacePreferInterface)
+             ExcessMassDistributionModel::ExcessMassModel::EvenlyAllInterfaceFallbackLiquid)
          {
             // left block
             if (globalCell == Cell(cell_idx_c(0), cell_idx_c(0), cell_idx_c(0)))
@@ -1102,6 +1102,120 @@ void runSimulation(const ExcessMassDistributionModel& excessMassDistributionMode
                WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
             }
          }
+
+         if (excessMassDistributionModel.getModelType() ==
+             ExcessMassDistributionModel::ExcessMassModel::EvenlyNewInterfaceFallbackLiquid)
+         {
+            // left block
+            if (globalCell == Cell(cell_idx_c(0), cell_idx_c(0), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(1), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(1), cell_idx_c(0), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.7), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(2), cell_idx_c(0), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.5), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(0), cell_idx_c(1), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(1), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(1), cell_idx_c(1), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(1), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(2), cell_idx_c(1), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.5), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(0), cell_idx_c(2), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(1), cell_idx_c(2), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.590909090909091), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(2), cell_idx_c(2), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.5), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            // right block
+            if (globalCell == Cell(cell_idx_c(3), cell_idx_c(0), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.595), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(4), cell_idx_c(0), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.615277777777778), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(5), cell_idx_c(0), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(1), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(3), cell_idx_c(1), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(1), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(4), cell_idx_c(1), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.605952380952381), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(5), cell_idx_c(1), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(1), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(3), cell_idx_c(2), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.5), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(4), cell_idx_c(2), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(0.573958333333333), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+
+            if (globalCell == Cell(cell_idx_c(5), cell_idx_c(2), cell_idx_c(0)))
+            {
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*fillFieldIt, real_c(1), real_c(1e-4));
+               WALBERLA_CHECK_FLOAT_EQUAL_EPSILON(*excessMassFieldIt, real_c(0), real_c(1e-4));
+            }
+         }
       }) // WALBERLA_FOR_ALL_CELLS
    }
 
@@ -1137,11 +1251,15 @@ int main(int argc, char** argv)
    WALBERLA_LOG_INFO_ON_ROOT("Testing model " << model.getFullModelSpecification());
    runSimulation(model);
 
-   model = ExcessMassDistributionModel("EvenlyLiquidAndAllInterface");
+   model = ExcessMassDistributionModel("EvenlyAllInterfaceAndLiquid");
+   WALBERLA_LOG_INFO_ON_ROOT("Testing model " << model.getFullModelSpecification());
+   runSimulation(model);
+
+   model = ExcessMassDistributionModel("EvenlyAllInterfaceFallbackLiquid");
    WALBERLA_LOG_INFO_ON_ROOT("Testing model " << model.getFullModelSpecification());
    runSimulation(model);
 
-   model = ExcessMassDistributionModel("EvenlyLiquidAndAllInterfacePreferInterface");
+   model = ExcessMassDistributionModel("EvenlyNewInterfaceFallbackLiquid");
    WALBERLA_LOG_INFO_ON_ROOT("Testing model " << model.getFullModelSpecification());
    runSimulation(model);
 
diff --git a/tests/lbm/free_surface/dynamics/ExcessMassDistributionParallelTest.ods b/tests/lbm/free_surface/dynamics/ExcessMassDistributionParallelTest.ods
index fa159c7b1b01c41933c16c886af01b6344a26bf9..8f0210d1c68a269eb7401a58947b64ebbc5bab05 100644
GIT binary patch
delta 17171
zcmeBbVq86)kuSiTnMH(wfrEje#M>)uB3}Rtm_Da4u}KKd+yP@6frTc&U{si_!YBga
ztYcJ|ENjQdgKQ31#WaKpGl<d`j3y9<0+S_}u{oB>ni0&JJcn7bKG?VavVq8+&)Q0x
zum4c?ynMuKuMqo@L_v=d)x6NG{k&|~*U6*@|Fix1=A9G2huw|GGtKkoO@8&S$mpW+
zk1*@q&OE^)jZ^(HZ@;<l_+N6&g6fa!iv0bfn8N+a)Hf*FIj&gjm=%4mV(A&azZ?(G
zaK8L4_g%AWpCac{&CHHf&a(CP*)Q%db~WrOU-5a`?p;@Pw}0h~l2a*9->{ms{^6&U
zYk$@l-uk!e>9JFrR4%{u3jK7-y8NDxjw+{w#Ov)XiEd%*ir;z3?Vj;@ouQG@{Pk~M
ze%UWL?WV8dw7;tx)jif+mfhTy<)veLB{AX1;V%v<CZ0)FYm)rA?beAHZ}Z$-w7dSK
zR*`d{=E?9(D=*2rmnW%9rn9DbhUG*|%+ua<DJ?v}iicl*^*R5oN3PfY)BL~Z@3x9R
z#s9<9>b;rQp7^}~1-IQ~=?w-e=8E(GdhzhI$lNCuhok4*vCX(0%Xh!~&xG`g+J_##
zx$8fnG3CoU=jFX!laD@GdN1bxug-0WFE;W<xz!8Hy;|HDvFlu0;+qUMRf)|>nU3OD
zWZhSbF7N2*+Eb}i^txQ2o*}@Sox|{krsxtz1_mEy1_nqH;^5!_Cm{wTz{SA8P+U@(
zlUl4-k(+ZjDmVYOnZUn&@ro-BY+#(MdDBxmv$J`vesy;9!px2Rtn#{nMuA=;9#($8
zUb_cPS@T`P?`=tN(=w&`*7t17kDE_E&uZ!9rm>}u_cRli<ugaN;3Ld}Y$ooJno=dH
zOtv`}<%;$Rh<}Ufuwh8pefQk9Zpl>6A0Oma*tsu=X4Lv3Wn@_D!Mm=1vKN~HWAo%{
zHaX6_(T>-ZR1XJSoxF(6iRt6M$<Ns2b@V*8L|nLVL9Z)N+(ul(N7TE=<E10(Tj?;Z
z(x7(_d=%V1FHpPNvvSgWxp3RbhV16`$ySX?wgJ!5AIyDo<^P`Q-P&c-{8B>_rG?{9
z6xdbX(z(!^vh1b9+bRbonUcj^_qO#M-+HtpPyh5(<7GuxdlEb5aJ07QpYNU#em3mm
zI-T7c?kCN3m=*b2DlKShOr2Lo*U7uBe{Iib{@>ZEEtR`^VbsI9EU89DM(#hvitjl*
ziJ1JH-Kbvf<}S`ZUmN$&nwp@Y?OVNZulC-~03P!rIlsKl9*eLIv`|z!qW91;#URlj
z-g@Vp#e%{S??k%wI;TZtymOhsJ!7Tit+{g_<s6@LYir5VEpwT-3SMqFpmXzp%eO^7
z<}F28LO!#;f4r5qy5NZj!{&Q27Y_tQcK+U^SKV`M)8nV1;q}{ZB@2|?j#l@5D_Ych
z``7Aq_UlSER4v+IqaP-@`G$#8@}#p{qZLl?S!!{aJ45NI(wu~YPh*y{zq%NCY^n8u
z)raJ&Eff!I`0$=%mGZHU4WX*RrS}SEG4n6&>9b#0#9H_4mHYCRKEdPhOPYRq>Sg5~
z-Dfd*HJi%rXS#<@My-?5uJ>9LCGt-3=#Q_N@9I8ziKnc4&T`L^x8>>Er`0EK-`iK6
z&i~==miLeU*4+91Q@GOJe!+M7C8qseFD6z^oGg0Fd%M}HN2@1gY~0rL@OjJ1zjK0x
zBu=aIln1tOo>eFeJ0j>)IC-Jp{0VnM=U?)QTw-?U-J8}m7Tv*3%CB$ETwT|eUjHXb
zK>XKBmW#Ft0`~*7Hm!I5wjsW)NwY3-=k#L_!!^!YXSx?&vRjb6>X|@Qz=15Q)L)s>
zft?p6jTvq}@{m=0;C6xW$b`V(K^3|SZhUYK{gFS#t@@=|+S}mAi7T>eyfk;c`~2MY
z^GikVk6gJdx0jyS=wT>)UnHpJ|BEGmxccYSt9JEuwzMR^Usdhl^siCpcDedZwWqU-
z-k2s&k*#;TQ0LcKx@Nw6;HM3Hzv?Gy*w)TcK78=5l%=6agDpQ#+m_m*g9e35Vk$B!
z-(*NzWJ%omW}Ru%w2l2u{`@K1j%ws~CtntOnZ8T;y`cR5T8nQv0q?H#Z(g`o+SKlS
z!?n5dD(gcZF9~>XLDSl6pU{&NB@3i4KKLCwud0lHMRnwIyV(=8H%-3eaZ2!6$JKRG
z*FJIF-xqC^yV0iVr2Sck6{lBi{dj)v*}s=r_166eIw5q^Jz!1Mtll#ZX0O%OYT0$F
zJbhh#Sx}5sQ{w6#OaH``3o6&o^quUWE+(90@qhl&4Rz-Y=GV{tvAeo0YU&dMt$i01
zj@}WE)4aC%Z{byc*FfIJV9#*%M5}lI%#B_}tG@mBhpl<@^p_rr>qA0{XJv-WI<{`Z
zYtf6wFGZi}pZ_YZUG#bSdO@uVr%#_gU3?+WM7OfLCG1D2%Iwu2TV<UONy}?$=B>K;
zKWyI&@zk=y^!;B>eay<0trzwR<-O$Y%OETFe(|@xKkuF5*!-Dq^HFJM&SkD;_EX+0
zV$`YE_@}->p)}*;zvT7_$2xk|VtsYBl{CEKzlxi`itg6ld3TM&IbId%ua(W#@fPpy
zd`y|RL+`@=b=hkI4m|Y_%PgqcpT(*by1Q<}ho)BR=pFxx@7sSpe?_42-`Y72@4OE$
ztM{KiJN1Y|tV6xPwl9BoIb1AT5X1I&Gy9AGw{Cxm2Nm0e?NaJX*cceh)X|IW$(}rN
z_4kVxS&7u`uXjCu-8$BF^=_-R)){}Vu3gJ?Y1(#%z+mS?Dvn#~|MISwyN)|HeBtG}
zJ8L&OoGa&3SASk(`S8>4Q-{;NPgpE^vT8w%rsz3^!WG>o-mEe(-1YoE*ZtZ5V=lcE
zvE1-5Nd1F?;By%+#r6-zntm0B7p8PAaX8@7%+yy=-^{@OsjWcjN_&d>wF>K9g4(z5
zL?*h3g}3e$s9PMtbNGXI#80Q2YgSx&{Wa-NiTRYEUseW(?nQ|4>rM9(KYI4->lZ&>
z1svX%FDbq4@BiNHQ?1X=@$bH#AD4GO^>c9_%NM2lb8_?DPp#v+^yHY(!4FrnTUB<>
zPxU!6v3KDq1%nm!@i%Nmvg7SsTtsGxhd$l(L;H-p<NZS`Jr3#3?fKIXW9Z+sZpx~y
z-IE{w7C8H?b6LDb{ObFBNA15i)c198hhMmRo%QgO%C6LuGaJ4tiXAR^@NVWw*2#_m
z8E%S&A4HBtDPOR)=2fu$WbIrpcJa9Y*V6`x9QK}IvqdtTJ6Yr=@2Ridu>8JrotIPC
ztK*K}HJ$aRa5R+4D;qA+yYv13oK3<>GDeMa_FVYB=6<EdY13cV{>gj@bzh*`bJ5fx
zP4k_mJo~=Q=8yMmZN7SG(y7iJnv#E9u84^?RY@{MPpN8iIcc&;IY9V9g1o8Ombx|E
z2YB3eevSQbYvO}nbJ}d%zFpqtTd&#HATq0o^}~s+jxsO3Uq}_GzEj;iBT4GsA<2Sk
znY)%Td=^RNG`~M>*_Pvlyp|=B$FHg`t;s!g;$-HzM3H3a4IzgXgk)X)|M=&TiawDo
zD)~`85<L~i1wWQ`NO2oU-9EoL-bV1BtZ1d<^UaTxub3U3qUu(*GfZ>(>a9G>Oxc&$
ze_@<Hja}|y#*Ov^Q>7QiOgU2NqBcwB0{b63fuheGU$>?QpDEoDf7dur$h{`Mh+XC*
z`%D>Yl~c1<J}fz;Bwa9<;r;@t2TbqSL&dnv4t$zxR=xI)NydcJ54`S%Ydl@=p0>O8
z$CI;1J3mw<?9S+{SoKq~QC+$|=S9wgtD=v7MH$r#x^Y%{_FvjR`&o|p*OSZ^BJJgQ
zX68(6i!y#q7V|s4d7;n0dHoN)F3CLUUbn=?a>>q!iKhe=ey-j*HSY(5>35d(>N30D
zzdB*SnL0<+ZpnuyvviA8^yHm%ic-3N7EPOYC)V{cm+OZ6CL0*rw|0vuHLT@6`*h9!
z{Ed4ecz>@ot*Boyt*!0X%Kyi@Ea&a{@$W)LYhOF__g%uvcxnT#E{vMDH;(_xI@5{!
zxvST#+7<2|wLZG)($&z}Irr{X$y{l?$T|04Z~o+uSu1{duVlXMGJCh}o-OmIZj9Zj
zX&HCDJoNv~bz5p)7e>hUJ^lYww7%-fhZhHD-TrHR=i8K>k9Y2Sn^|1%a7KLla*sKM
zwKw<<-_D-Oe>KAGQTC0O*(E7FUYGjBcc)KV+rB|>etX#YsEGb)|3l`zDLL{umft?w
zO*7~5wTRbubeGmjlqpGD+i%$?>DANyyLFkx-l==yV`tR{+f-_=DJyvJ<?JPwM?0?n
zi>NM(Yn$6=^|G`x_1@15v#<2bi>xoIeirr1Gu*md`EbtGGkJTHXRTeBc5eBxCHF3F
z*|_U+vdtCoX^u-{Y&Up4JEY~m&Q5jl<P_J2sdC$8O)Pxxovr`7ro{Yx?#5TY3s<IG
zcWB_(FpB(i*G4n**G0eSM$$X=RNh)991slrYx#A-bFm4L40UhI@~cxP$W;0{HG9-^
z)xUDF|HQ#ya8mTU<VmruKdOI<%~34UzIpZ{hwP($+Vxs5|GzB>e2_J%D!|jYIO21b
zLWXpL`T>U}&!z}V7R8;3VE4{A!+c(i;o<(3SGTG9m48x7-K((liF@;u<lS4Od6geH
zUrW8>{Uk%u=U{eRmsvm0Cylph_l{3{op1OtR&qi8%k~*l&)=Nc<8<Xt&GbV5TQfe{
zyefM)cN^n>M#;|YZx=UMC^2xVZ;aHqb*4yU1KXEBH9?me;`8m7UQGS)?9#6g!)b}4
zx(B7Rwlfw!5PYe*RXXI))6?^kZkhX^{SR)oS1x;1bA^?G!BiDi{xd*(er1XMja-7#
z3=AMF!7w?_L27cCka9iK-vFNwS7v5r2?+^#Z5wrU0W~!>O?_KkBXc8NJrg|x3uA2y
z3kyv%cMBU=a|Z@p$1rOrFAJ9-Cu0p~8*>Lc2Ol4XU>}CaXa-M5n*bN<*hGc|KZYb<
z%k)f!)DniuN`~BAz3gn`f&w!a7Z*3*P@jPCFmKmjzkui<uZa4H2+z<Izqo?1sMN5S
z0^fv^$fT^uw6X+$r<8E-<j{cRsF38CsH~Xathk7Rc)y~!;DorOq$H=Dgpi!1=%S>s
z;^fGZjJT@Qpo+A}s;rpw^z_u6vaF)Y((IJd!tC0@l<K0aii*m(!j`nWri`-o%-V^?
z)vX0}lggSIavSUWa~h^pHg%P@&8X;ESUYi9V?M*=I)=vF$hN}xmi(mlvb46El8Loh
z6Kadwn;2%aGEAGn(9~q#znEdlN``go8CLFP*s+vh`*wz|rmCq8xzpRq7WPyun^e1d
zcgWrY42SnI95};p>>9(>6Aag`G2DE_@cbUbt0xSvKQa9K$8hv$ME#Q|X|1iTE!~rw
zduMg@PU)T4-!pOgl-{<Ple(r)pWZfoWyjo2Q|2t0GH=zaIg92jn$x#%OW)!hvlgwK
zx_Hy1r8}pu+%k35o|#KG%~`c|_L@Bl`Wu!{Z(lNJ>ZZ9J3+FFbv1s=8MZMdWO<TTv
z`I0qT*Kc0EdeipJYnScWymH&NZ8O#$oLj%?*s>ibR_wXBdH0bm`%doKdw9*ktGf=L
z-hA}xreim^p1ia3*u_1kF6}yfXWxbUJI_Acd-3ss)zc2Goq1&A+>_gv9@(?$$i7{t
z_HDYfcg5+$JFgyEd*j5mM`w3FzOd)Si4(`pTt0L0%&E(_E}uDk>D;NCXAj-FeCq1e
zE0?a`x_<NG)th&3U#q`(@8;z@ckUd&@#6Ho50`I0x^nNu?RyXJKe~DD(fe}`KHhxv
z>gJ12&#oVRa_7qH2WOr?y!Pbb-8T=fynb@$^NSl#o;-Q<^yTB1&z`(^{o?84H!q*O
ze*OB<o6nEm{doQE<C~8!UVQ%b`OV``?_U0R|Lo_NSD!zB{`BqVmmgog{QUFl+s8k@
zzSRHw_m6>r;s5{tD_-B*#=szc-qXb~q~g}wx%Dx^>9Y=eJeR$FO?h5w{_VZ5mv0w0
z;Ok{wn_#~E$~{ZY7h4XzoB7~v8S|TWcmG*P?YX`AUF=@zu-1mDy6<*{vYeUFqH(F?
z)~&KomL6XFT}QeUPQO)ekgwaBHGy|h!ldW@)%Q6=6zUuO3=9v>F0Y$V`TT6kKAZEu
z_niN|@AE!k#{QI!2OBGN<hqYP!bWjr?s)K}FMIXeLW_vaKQ;)xk9c3kd~d;FM)Nkt
zJF@Bb+xWC2e!H&?D=~SstFU}a^2EfooA*xH{&RZgZnM8xwT2(tPdGJ953TRqe`AS~
z>FH3WsRb&_XRMf<?|s%kHs{i<3zvVNU1B}`MCz&|<w+AZa9BipG)$br@bJvOFh~2d
zC$`I$cZx|))2&=$uABKK`DO9p@c$C+8<G~RnNVQn5v<54vdp4lrOUpzE#7-}Y%qHN
z<j&5x8Og78wU(~1U$%Hg(937ZSzW(O`0F(^UU*H^Tv~lK{f<cfgGwa-R(;vv!*;ke
zVV&ZkE{-w|!D$EQUpd?oFMfU{=TsrLD?d9*v^_uB>P0NQ{%r4#HGGFWqfRj=EZ>>1
z_Zp|cf~DDSSgZdnXg$2`+B(VQna&Z3Yqwgy+9E8oQR>lK!>-G@K}8u)IbF}!`>2|<
zOchf5w&P1nEAup_<XqMyUe~4Cr@cR^Bra|-wNpBOe6~JEwT0K~Bt?OnSHrWVruEi5
zelBp%iz9cHkoNRFS1p(felWg}6+Nn~$F?fX)8fn&g8(+W7B30sWfOw7c&4pWd7Axq
zxBcH+_kRe^>rr%W4b3r|efx~=vu!h1@2tOh{a1ecy^YDorEg!{mb^PX`Tw4SZs&gV
z*ysG8b?S}q>7|zqmQDJ7yv*&eN%lu|<x^{VV-KE+4i&q<^8a&f>*ej{|B|2Yc{u<1
ziD>7GY?qg>KG^ta{ndvIJHG5Wc%_h8etPKP-dy#jsoOU^eAe)FvizgJeX{md#n1i!
zf8Ag9=Fj)~x%mZ$yWE9@`6vDi+G>(+df<3;z{-DF`7!(UyotCp&-8e6%7ed$e|_6t
z_v_5}|4-G=*Szkvum3EW7tud)N!XL1i}7Atcy0TawDy^<jC|=6^75a>30pxO)i2zy
zf8W0U=T*Gz?>FJ|ejfZ@|7VT-r6BR0ES=@+{JpYYT)p!*eAelDudu_f4+bAH=2KT+
zW4>|Aw)cNd+2__5|K7g;SA6}?<h+QL9NxFImz`|dQPXl`cH^Tb&6*(-e^rQ0Ezyd&
z6cikyC_ee)nav(H_P=hPli%}m@B7^P$L-ehYbOg|+wy5okS>31ZMN98=dI_D7iDib
zw;&<3<cHwD->2<oi<SL(e!i@Jfo+_c{ln6zAGf>u0z{7=+QBkOg>$_wqwbS8#YciR
zTD;smRiyjoRG&k?Rmz;@-!K2sWT2O_ZryG7H@fQP^FMq$vPXP*_My|w^G_f6y(<5#
zu+iZ&&Ck19Uq`!fPOa+nS-CJ_>C!icH>}J5sl1lWQlRtwESD7NN|U4CJQ6nMO0K9c
z+f%lF*|I5B^A{b8;e5PO=-efzh|o!Mn)lY5eSD;Uc-f`*m#^09Dm~KQ;r04an8F?X
zy27L!$vqWXIU7RG+8t6qyRSL!h4sBBzq6ODnwj6spHjeL`QdN0jadKD<c|?HVG7L(
zT00a&;@W0>Q(j{8&Ogh_%cp(QrTYADRo3<ASJd}syihyP`OokEyRAW{ejA<#Yzkao
zT;e+;)aRc4_Nm?M{Y`T><ZU=mA@fe@L)K}I^?qRra~^+`*{GMx_%%=>W|~rQTdgo}
z<G;T#8;u^E2rP(Je7)uR&Vtt|wKt0b&k1J7JP9eWYP;9?zV1n>-WfUbMn<jLDav9Y
zf%&|mhZM8w*Z1!cP4hJ=X04xJ$t64O*|w?G2Eukqw&FWpKIU1vv5iZ+t3z>5^MntQ
zEZ#Lsdp@ds-_=);De|!QX_3`|_BQLZKczn1ksE{-2~{%vy|-m=ZON^z@AKtUOV0ec
zdwbsA$$R@g|2sP2<czf~OOt|Dz4Y4DRbYNM=P~yOA<d~TbL8qjb4^apXUgQf9VS|M
zF1lTQe{;UYrDKZud;clcxhcNvSMqn)mRXh^Y7k~Q&Hw&^@VNGMfrb}7rb|3cwmiC;
zIZo!j)p6O?^~}d!#l2@fefq$DJ?%t3S@n%u)+w*~EUBk-GpNj`aF>5md-d9zr!yw+
z=8S%qS9$H_y&DIj4}HF~vA*GGlZxLZEu)!UtId5pSL>KfUUSRq*0q2sJn@xN4_?`H
zP{uRx=O<p<$&YU_t!&{w@T*Nfvodk6qt5Y@vo&t>95pkSRt^8Q^ZGK!ca7JiYMyR=
zA$8K6v)DD%EpMXLhfAyKEw{C4haK$N^KAQ_RVQptKlfgp%aSX0+4jrgZ=XJWs-NgT
zLoZ`XL-cydqu(3ZPtKaNedeu=V$;{16e!TiuK(|Qu5$I_!-=i`{_&+-R7#fiFPr``
zBIHW&>2RIr+g$YofB3wOOgrwT+u2hkQtK4QDc`l_)|ENDHd^&X{)cxgsh#3nD3hi7
zBrZ?!yKgN++mWM(CVmKadKV#|f3QDi(S+)Hn>kZ1q;I@EeRfr=^0e6(gb$lCD;-`n
zZ$tPYv)*N$t4~T?zQ4iTasItS(T5%+yK*fKuhM5)cp!XFLg$6_PnOe`PCO)#7&yh`
zX!X>fpf0E1H?{kGK9(MO-gWu2tRp-VB%EIRpl!m|gURhpuZ6a{yzu|DbYjzoRrz;6
z*B@5jWB%OS=|=gR%U9n<hn|r1KD+H;_>ZjS<%ibnyZYW;`p?wq2G^E%ZgUMV<P4Jj
z^C<K541Vo9S;yDTew4K{DbyzR#<#xRNk{L2ocwRwvcz=}Z_nl`-%ZFfvpy8h$sGKW
zf1!}w#y2aU&Y8YKwMpaP8q=(wT#R`|;uY~38@JRSEVnO^u30}N$TwfT^nJ0v+1;Jy
z!6BVpx}4`1l(;$`kgEQ<z}_oE<kpUDHl`^SQm%ZxjI*M*3g`A5&9(YH=N_jT!|oFg
z3uZr9b!^kyBeA_!${cEow=Oy%uT#6IH~U_e!}i^cEA(`uq9bRCXWvWiU+sU^)=q&@
zqF?ge4qYjMdQ;(@%dcHeOJ{NBl>T#Qmd>sn8i|L)8m3PRnjL)ocF(7rO_$`#J?%u|
z%eXwXi_Bx4=X6h7>tpHHV5kxCyzZg=G<h$M4PGx;JZ3nQ-}|l3q0M}x%Q}KH@So6J
z?INq~a|NvQ*_=6<RdnUOZfGwo>;3ZdgwE6NY+G23)@I+gNUcvap5)!i%dz6+sbA;3
zLtIM*V(wmh^eM`IgVu~ma(!MeE<fsU7Yi*sbMbUR58J*wUV$sS_KL{|zHxFtS9s{V
zc}q&+imv3Yg9~e(S^cia3UxhK*!A(JY*W&q)29u}io%@4o9Az~UK3PlpQpra9KPMI
zbGPal@f#aT-@W)?H+AQX`t0NJ?h($XYtBkL>3_Sm@%EdbIh@O<EkBTOcU|L};D=MT
z*JwSw$@1uW%toVdFTIo<#=%dz7j&u_94+MdEav(C*}i~@|CTIcO<8d)-_@1P`~y;|
zb7)rH!EG<@>##9I&A!R)fq$0pN+a8@b$VYj?5;@VZ7yQv=&pa?`(&!YlS_}L8eE8Q
zy43FgDx!^oJpR>xG<qZa!1mkSmTl384_`~O`Mi2>s?oDV?S&hc&fVFVzC684w_M%3
zqi&h>`S|#ti<#G*lTXh%T6pcVf!<76&x&6W+P4kE-kZx8Jo(L}yU=!Be2&tb2U%vS
z+X4*V%q%?Qa)V)(uXJ{OX5(2`tLK|qZialFu%u5ZTIf>nCr;6y_Y%8g4o3XAWT~7w
z<;sqwy=VGs9oDdl9RJ$7zfxM{hppA#q5@wo1Lh)sChN)_#dC$H$e&H++`L<e?Q!_c
zb(1d&XXoy`I{iuRto7lWFD+Ve>Pg>=cXvv5E-Kde_E7ubHevTZN3ne$eQoRKtrWh|
z>3j2H=FM0J4)59$zs$e)JoaunWhtWG#FGB*<vra)VowcwL?0LU<QVDSKfdDW{|~=E
z-e_Onl6uv{%u_i+I+5#jm6mnx>gXQpk8=x_RQYB8m5Zz17qU&#v!fu-`s(a$VOte@
zKa_lN-sHNm{nCl^UZ0#5)_ND6P`NKPz5cPKew&K+lD|KWuJJNDJeBY9N&~mG6Z$r2
zz1n<dll8-SqIuB=xylbdEU}h3TyVL|@4;KWU!8&5ng1DN@iwaZ%)4p)e%+k8c?zai
z&IK3l-uaj7`qQQhHB0|)`q}ia=(p(S(9Jvle){|L)zj5!{+mvRm#S^vc{oQ+`%L%Y
z(-FHO_SE+z{Azh<W_st*_Xo|9)_l7UzVF^A^|M23VPm*c9H;Zzmi{#9JqHt37#Hx>
zsT^II{pyS2+xtbuF@HR)ei}*dyWI!v9@IPOF8J^J$42q_)T%r4uWV_!^HzM?>!KYy
zb|qKckyw9yf&H>h)*ZXvUCZ1Ya4Y)Ik*i0Gi+UAw>JMvq`%Sr?vHa)!=JO}jtq;!q
zx-`$`y=m+17i?UImt3m16+P9*Xzh5%Wzl8(%)ejT(*@m@iPYxy=0<OQp7n5H%k(D&
zV*9;)ZobT(^W#qh=eCuKrQcTE-F=T==UTI-;=H*ZL>R2E@Bc2c<@5FJLUZQ#iiE5v
zvO4S;H9>n~U+RJ5^^ytad!|JES@ng#`Lxy7osZdO2e0jqSsL$td5c-WQ!(Q=?ip%-
zJZ*gp4|AM0J@_$h!<v}MwW^Cf%J>b6t6Ca9sOx;x{`&Y$&4<~`kNIx<CF8JizR`lb
zJMnv7F5r`Y9Fu-W>YP``nHP86!|yG7sh$17V9H_3t6zD(%4wa*-1S<o-uJuvzQy~D
z|Gj3MwNI1jMRihrtFP{A!?c)5nu1Zv!DnJ_T$OFp^*?3IyM9MU@7(K;H(PCVzNDyg
z*Q$@x=j(b6tLSW@xihb~smwQ7`Siyl&uvdunOy(1t0z=B)g$WfGd1Hc&%N{8CU1%G
zo>BU?Mp0mOPMP4bSHi|kO<CrLv?kY2J*Ix|U|C$_hAEY&bd&hQBDd%))p9<;r?FmJ
z?>>92Nh+tc>KWUM0W2+_9^BN8bZ69CcFpx!$m5(XD|bEEdFRlk#htpR{-&;9Qynk)
zexg|Bq%A9>K3}`4C2&o5^UlS3I#WMXy-E<D5mWW|h;EMTm#w)~+%x7FEPHva|5~W?
z?cMcdzTGdytPa)9;q-Y|wk&(i({0OsGXDA5enBwg*KUC;+Yc?Z;4=ujFts4c!aVmM
z<DZJj7qeNGWj%YCaNcR7Tz8jHLEPeLKPLWI`P6$}M49~RiH1`|#p2sm)tdhfHT^dA
z_r$*%GCJ4#_OjQ;<{s58WBxRk=iRELl&{Bpj{06$@v8opU(^omhX-D*p7?Y9md?JM
zzad+;txi(8*mPfW&F(p|&JBlDUd7$t^Tue^RH@6iv;%9^w>c-&lt!#r!#7nZ>|wa&
z&SH(A>d;4X*k3;iyAe@vKsTu5kQVPuyIDDh-%SkK_HD(dB~Km|-LzWE^4q_xYfako
z?|vE<Z1w(gxh8Ho=34*#hIz^T)9<wE4svHl9bIC6?_bHkK=Z(=gq{SoDe7il5>!QY
z>?{8AE7-mLQLOkcgK00e%l9n4|09Br^Z8T0g`X-qHeBFc!O!rUL#|?PMcT1~S6#83
zZDI*ATDw+Hy!SlE#p&JZwToug^J(V>xQF*W&f-kgUtsUBY!REp@>zMW>OX%>+u6RL
z#cfjwcOARTs{7x5toZ7`y`{{^{n2N+*seti5<l2=`ecus<+Zg~zgX+iu6dQG*X~^9
zFD$^ahJ9<x+Aq5UdbY63Sc+VI>>tg>mA>BBUTfa90Qc2%_*+EJJi4)4_i^6mjQ=bp
zH+I`gHtsvUz~-jS#lovM>m+}zJu946zx!m1ohjp%zxi{X|GdbW?8^0r)BBItThrjx
zb1wwD-z)2`mtNZ$wme<F!gi}ksqa0-%9SyS)+*w6ujWs>?yYs{YUa|7`|f`@@l*a+
z!Sk!39;=Q&?rGh-BBttLpKz#Q>}t;E+-BiDk8U-zG%!y5Z0dP#lS;QY3rkDkjLxT7
z8`bN-rF2#cT>IrK&wEYFzNnM)#s%YTQsJsc`&hUyd8fBOTHF}%UFlGfc53B?UotWE
z+%M|9F0Fl6bkXEXV2F3-`3rKlG?snq51g{C|BkNnlWsQd_J6+b3x6(JlwO#4_fbe|
zdHLZ-hpJSV`i~~p{yXpHqVqBRt95C|hCfo*p5-sSU;jjP?WG+epL+V2?A_>Ra7_Oe
z+txMw7uVg1OqE>Xy<p<H7Oq|2^6YXA-R7~m>a21p<9+tLSK{T~hj$h{v&~Y}?px$`
z?x|<uT-UoY)(clu-&>`xy(`sd+46qR$G@^}Kia(dw_QZ)&I0u%S3T^t)h3jlddTp7
z@>K_mh2@e{i#92_*5@rhnU$RQ>0$ZfbKcxZ`Oj7M-1nNO-0jWDzb$^>%xGcrT?@l*
zx|A5_R{w4d-MV&?ckA>0CSGZws~2m}TJ`DE?%F->b~RHjNUsiE8gx}^!P?X3t~k_h
zWPBA8tJk*E@=DTr*258%-S^HIY+HTQG%?5M$MN6H(lUbHA0HP6#)Qk)e?EAAuitF`
ze-~0r5Br||^H=Nip6%&>o*vq;MsSPU`pel{to*vu8ZW)_HE!5@a@}QXm-5*Mp5!F!
zl*#73%h@_pYO7y^<Fm<n!E-~Gzki<+-(_=RK}zU*GZCo+dm9QreB+QZd}&&>C+llR
z!1}Z5Dp9?0lXZ?4Y6N}1DLMBZdzfr}t9uP!(7t=gMpL&J`LEk9YZ(<99p&<+PIyw(
z<1;ObqP{a4^~}}Ip5o5f@$5*me|T1w_vc56hdFL^PV0T|ZniffZpXsscW(bn?sYqH
z;cr$+l#T(<imNXd&2X6+H~(i-NWW^!jb-N)tn$|sPE5XRTClp~XWXu}JC?2U+qCks
z`N?`0iPr@^IZLM<`FkgFZp-F{0&kK6{&z8$PMx#y;vBAmzo9?Q#tH7(w|m3Bg3EJR
zH5GT2m+a@!=IIHPTN{2%xyPq-+SCbULOpkXR;aI*>3gv9-+sIMuOl~EwReP6Brf?P
z?|NO8&#`va?IrTI^BF%>sW&e7FFk4@!p^DSR%HAtynbH7|L{{%ElY2`QoAHRVf#C`
z3s%$E^(Fk*Tgt2rT;~?p71F;&U}Km$=d$lV{lc~h*4?|lIpz-6Qr!z0#uus=B!q8t
z*1i01+Oj~oRj&da-+O+Ue$H+K|Eo;qT+s*5?o?I%-@CV9-!ic{YfkP!r&*!vytcg1
zE{(`I(>1BI^7ogXdZl@1qparajL_SXyl3N^MHhRo=QSvAOt)j+|Fc*sW=dttakW0p
zv~AB;sctaH^yTChTxjd0t*|62$eA-)Bgb;XmM^<B4?jGdViI)gqfohDSHHW{yK2dG
z=d%O%n_fL=kliKlx@p!UcV&&e?olu9Z9H+|?&gY|HEfECel^oRo-nHSh;viE^x)UB
z1ER;n%BJj?;Um=TX~e1fV)KlY#WuTD&PNt~`oO))^03_c8BG~MM|rhhOYP`j@GE=Y
zq3|aws6WYs$N2cN&s{1SXJaCkrpHW@>DrPS9OS;~&CBe7{}FE$yWXg2L^~{T&5oG;
z@S>IOIfq%cPd)yctV-g|{&U0TX#KP^RdekU>=NH)PPf>&<abrh=kn0Z50~B_P~CR?
z(9gApI5`BwviC@Cdy};Gu9Q*M`mpVOIv&RZd(@*=uRg-+z_9UH_-%cG(u+}Nf12Fr
zm|~EZV``Ejz!>B-^SE{ruUxjg)}pO9KUVGaS{<|Yb4NzdoEhi#$IpHK@n4mp>}((P
z`iP*6lLn6Kf5^_$RP~;AL6>X7$pw+yPX@n9TD)Al|C(%6Z;793g3YB*bJ8N-9SHRa
zxH|u>Z<)oxqu;c4-?*3R-mt+)y*v8agyfGQI@h)1E*i7W@H+g=!t7pg)x|5@Bj!)g
z-LOn$n#ATFMHByH0xxf{e%m28*){4ZbI7mX%elhp!_7bWE}MS$@8&Z{?!L^QyWe(~
zf$AThzhCD4KK_ika@PqL|3|NE<&@d3=x(rZ>r<Yw><jnR9Um$hKF7~_bmmpk@&iT2
zlaF^^wmxu7?2&D++k`Fl2j^@0@-Az!UG_U-sqL#TGS751GH%EFr=EH!-QMR|$C!C?
zdFx!m36u3#MqCT3cRl$_<5g$*E6uDU9!ssYSJ<9N(_6#)Pm!nYzXk8rodL2gJGFV^
z@}{icG<P!pk^Z9xPtE%rVzcLl=wq?IRo}hdu*jUO=Hp1;t@&L1h)Z&|jfTJd1MSsK
zH<J$~ZS8n)BCn;?(a%CZHD*flg_#WECv`b2j`<$Q|CVrpEmLbvai5W`WMIpez2>5)
zR_V7YU;EGK`XQ}os@od}&?+}iS3j3^P6<t@3*a`}s}wNSgH}<3)W9$|0|P^HeqKpx
zUI}O|+*{kf{f{g*?ccwjY2)I{=gxeb`2NCf+vR&_&bz#)ZRh2)Ywk@BTf5vuqC-t3
zM(9`a|6l3qH7XMvEG;@Ym#IoQO$~m!Y4xel)aTdt@70-eP52Y@?c)5~LEG==DVD$S
z%_%Rdzy0s;@8^9t|J`o?|Lf{(zqf|FW>!4D%Cs{v{mo9rfA<gF$Sc_VC9q+CWa5<*
zr^@qfv^O+MUyoYwKuzX;h4+)Zf_%QhyhXo{T|e%)pk@2pN(sIEs=d#jKYzFR($_zc
z?|<*FezM)KBks^WwyJ0TYJOr{?!4FD8=0Tf8ut98+j;xkdV{!U50!+SrFhu(zPPTN
zk?`C^_v^oMlRswj7PnsAcvoS|9m88j2HXD#+~O82nDqBSCfm`Y3nKWsRo@+QXIGV;
z`Qw`L7Ez||*10a<r&}`$^hErSzPEK=|Fn7Xc?&vi7MLq<(9Am=&ffS_^?>IaRxVu)
zqekCH`z*HoR<4(r5N}?;Aeg^u^7I2QefG*MtO(99z9;hdWOQQR_7uJFeZqp4lHVsZ
zb9h~tyz5e#RmHq=m!d5FfBU4>PO;eeO!1sFd*yTexN9eFORtS~(D#0Fo8@Mafv3d6
z$G*!d7PXxCKFxeLThQlwMaOned~$x<uC!Nc?|80JGn%EIVZKuAoBE-Pb`$G=*)nZ@
zddlq_%dx4~BUygy2)@~Jg3~VM&BePyKNf!Fm@g+-AapxcS8gJ|n8CFv?8ZEsf7X;Z
z*#0(q*U0kYhlYh<VAsya%w-q48_Xp7!V?<SRGVIO@@kEEXyeX3W3S*p!zyXs$%o}`
zE&i=+Bk}v2&>F4e7grL#iS2yWamMhAOZ}IFVFxGPX1C-h(*IYP{!(qZZtU)@?@qVu
zR@iQzQ`0=-!Gi634UXD5rDyd9rZ*k)`|$93MMbXu_DAoR2<P2)mhQjm(XlpvS~ut6
zYq`w3Hq5<#`&;~)rQ&}!v5Uv&Y!C7k{&9|HXNACnhY~io9VawJ+<GYC{rBXjw;2^*
z{U$s+TmN>$S5Di=b*6_cer|co=+sfo>$Jt9eb@T97p}1vfBm@seQmyc@ygrmYi3wE
z{7+>xUc70#xz_dTYeQco<|g!82U_2LD7ANag;e#WZ)w@TlucXT?%N!-<1KI6r=;}G
zvn-#SrCHamEdTsM`L+9V!H1q5(kk2UZP~$AzfIl6&@<Mx{<`VyUI~v2#XS}~wI2P7
zy)1L#!n5b4bKcv&7i?Rvva+jQN&NYVd1sxAiccDRJlik4hb!QCblbN>MOL0~F2pNJ
z?JEAYMd0qLm9Iqw<)UuN?%TQJfSCNxxl2u*=Sn;lo)x97e&?~)!N51EK5r6UMy$`S
zy0%s@o?*R;6?cA9bkU!BzaJikd4czqIQ4D*nVR`C<)dV+rI&%D)fDE)+7g}P^LEZX
z5dHIr@$6grvyXjT#TI#cs_ws&MNPW-{!0|!>^<^g``gyu>vK8Zwrg%q+qZSj`?E{$
z>T-4;iC*$@(w>zu#nTh^%`;qokwG<&;pe35feW|m?42%k!B@0bed&>TO25w3OZ}hm
z^N6haw4;Z!`{%1&?C$c|nx*6{|68VTa!5;7O`r8M<(`<hh<|6wtoddMDZ4$LxL_0O
ztDnL-N9Fb!sAw;#yB;t-QTu~T^`&1c<@er7Klk{nZqmD@>&kBY<2!L*;+Kw5Swe(i
znt5n@%PE<Y({&g6EU<D}60WuAjGTy&RbD+mo6MO-Iof|TCfh3>6c!5k_0``<@qjT)
zMu7pdWY>$3=kbeHCx-1~nfXXu`(S~|wbfhAQmt8Z+O5o$CY{=%&MSQ`Xy!-zy3+5Q
z+GnP|ns$n3OYF9eA2Z!j+kfmZ;hwI#DC$w7u=b3_MJ-RN^iFNR`rG`R*lD>C-J*vk
z^JZ_jRv+kp{gV0f*7?^20!`CCeP92#tvq@rchE9E1^&<L&n!*+cy85F?JQ2!X&Fj>
zx0<$|y<x68XC}waTMUZ69n({%EIugcG_^e>Jk{_Xo1W9vEaAknf(rJ*8*XXT?v0%G
zjPt|q#07l!lhnnE_A?*XUbXA&$}h*RxY)CQoBtzy<$j*>`s?#99@GCIK5g<O9x<6u
zvbSA7YTW$6H&Y;+(cRZ=esF=HhsE*aM6>rT>%FIZa85d9W^SQyD7ux!TJztFDueB6
zDX)((`mU@#I(1SC$IW-AqL0=-jChxmdiDdq#yTO{$A(hBcFeE;zb!M1`SM1l+lQL|
z7Vp!(*xO)kU-PG`!KtiX-SWlLZMNILPuyl-dvj4P>-J2;#rJCX3vPS4J^aHr#iROi
zn$G9H^4s6a{E_MJ;w_8P@odQDmQ>i7buwS+pY8Hik0}`&=R|*8;M-++=-b5P#hGn?
z_AVE0F5@n+NaJ6Yp})PX_V3}v=9PDIFGT!0F)Kk{s#U(O_jSd|x;ayYed~|;H8=(B
z+pYQW?pDjK8@o!@xIbaJT_|^Wf>x^h*#ho5_PrBi=cdGah+WzI*5}wXd(JY|Gavge
z{+2Sh#($rq@e-S5CZmUCF;{-<{&|d2S6{5po|?Q{FuF^p`4aPX&HP(E+r!p6Hv8pp
zUwE=@?X=44cgz;gP&&=#t#oM*ixRs}J$uWQ>0JGs_p8tC(7x%q(On_$y@>2B#&ByD
zkL5`(en~Twnq8b!y*DCm`>6wYz1z%lww;eW$F=rvkHVLY=W@53Rflh`JFTJ^5w%X5
z+l;qir}rPJk1kSibw74L?ycYQYe(^;_b=m=O7hn-ZFJ1Iq<MvR=Lz@JnvSDc8wxa8
zU&_>L29!O~ys62WKC2|x*thZ25AJ=BwyWK!-E?=;@@U`Mjrl){em{~g*5`=s``Y!#
zDYh^&Yr&?xhdcQ7l4lhxx!ICk=C?h}c*WAi%~3abU!CtW&8}5rZ)9jQ$^Gvhy|U}$
z_RBUQC8<J*b9|3XpVmF^yt<2)&N~)Wxuq+vO_}i7xIV0}^!B%9=G*H_-fwMRyd@{e
z;No>ZmcrY8d2vQ_t8LS^Y<?*(wphM<qfTe@+Xy-D`8$#|XP?`BTEgo2YB{$S6VJaq
z-2s8S-ZK>)Uidk9_43J=TxL8MKA3Ul#Ge^DdmHXa9{Q@hTJE%Ir0a|4cTzrTO{$$7
zy5{w2v&bbe-IFux>+Mw6^j!PN^@6Ks+PUV7QU%6oB6U5}4$i)`+$8GG#rl00AN4Jj
zSsdDPZdLEKpR3w0O5{!Y_3(1a!S@<TN1m5x=Nl$T2b;P0pIB)VWK(LR?eo9r_Pg|A
z*~57&+d}x}EYP$rn(P}K-W+)Ksu%Z@D~>nz?KVEKEwXfiTcqOw&Z|f3(_N>Qm?oxq
zC~Q(#s(&^55tq`u(C?BO3=@4r#67~MvnJmB$RSs<{>t)zeHVqT9p<XVul}@jrufru
zX&-k6uKl)jhP2afxmf$_t7|t;za+!6;muT2@l)YK^Ypy~gMGTaUS=J-vLod#_x{Bl
zj;0!HUpaR+%I^Cl*s$shU$D;R)v@&~7j-tDF74%iv%x8~o!x=gc1M`Ck7%RTR`c^6
z4l^Bg`3N>PN%06au4>hoC0Z8Fs1?`R`6%?Ih<->r@47Nh7ej5Y_vee10@x-PIlC;#
zy0}kVu5nUBanP!onRi1QTUPpr6r9_~BiN{=_R7s=L5V`_t_}ysQvE-DN=#Q5a9pXs
z{=#K#5OY9)2lKwnI4z~17nQu}JyJg=3j8VIo_<a`<ha4j%Yy&aH!l{g_qy}II_*RH
zn<L)!xf8#C6;a$Ef6u7!&F7ysA6m{ZO})%{>;9oC<<7t+w-qU6bNAogSfKxeH|Rsf
zy03e9j%gccJb9Rz?|x^aclh5wCEXun^>3Ez?5S^^F!lY5ZTGfb_<z&->yC?a*NJc2
zV7T?zL$2S`f*(b1%lmJ{U7vZjOkHp5n+2{6&Ee%X-)*n|6ZSo2U{SI!Y}#8@w+FHR
zZGXCpDzxab3+El3$vMmB($eoA?N>?go9tb=N#pP75N=W5FFz(aSJqx%_<qkRv!WHt
z-LyroUhTWCY+T>_|6$eA*E(F{-=-A2G}4`_b;;*@k9Mrf#Y)+N_oX*V<nJE(TjYMS
zbloMto3r^^FRiP|@H%yp(cQT|HSHhM9%Zea&1*~Bdv{*buf8ESvoxkZN^qipVCO1j
zp>q9X5f)`_4!&lgi@D9y-fm0V_x0ANnCDx!a$Q)``aCo{<9~W;y?<S?di;saGnZYH
zxGLYJVg8o6{K}~XkxN%Gy>^b>Z8v{s>9T*bd_pxD-&(pYT`D>4)pPq@^OyW{cCY%~
zSF>pK@->rcX2r~zQ~V`PPWpmSQD?5JUCpaB>q`6Mzt<<<ym;-<v@<0SKeTv-GF@%|
zxr$}O&t2{dqb536_civ%mo4L|&(l49;@!%S+fKhWFjX?0c-oPwKUMqN-P8^5>;82v
zxxe>9(WbfY)2-)JuintA#J+dy0f!^P<x1OkyLNC%Oo=>kGwjXn_lxWQ{+>U-?*9+j
z+nf8FXTO`PV*97b-izz!8rwa!^O<%YS6D3ZWL`rL2ZyVf)4Nj#gff{T9Thjt5Olg&
zAJe&L<K2y$XC=I-3OOr(cmL^T_C<ajt0sPx{*!j(7i+x4NvUgUPnf2fHHPjMc39}$
zqL*2={oUC^e~-^e4O9wu;;dPzJO4k=bPvXDm!vjqPzzS9)#&yt+BkX1w+%J_0(Z~e
z@mIKL?)CdJ?T!i@8(0E1#O@3BdGuia&mV0&e&oNazn8Lxnd43dZ@K*8-g^ddJR4p;
zK9tSr;VT(wcJatREt^vo%~3*yayBj()1E%GopyJ*`KEstF5mn1$UDSZLDl#t`(6H-
z$F$b&5iYJ0FRYsWY)Z`^nOYOh%jH{qw0EtVdFmN!s#WjNq|fV}#M7>)#2Rkb&-hWv
zzuHK?>i<EGEobVlsj74w>R6<~|NhbB?tAX9imjzG`Hffku5ySq?vap{GqPHeR<d9=
zTgCZOp4eG=2TzAe&8<IKJ^ysgxAM3-50vljJQg~Se~PERnXZcPj;)3tMQ8OSEIrQo
zRKoj=Q;P1@#n&e3onNWhwN7){D({Qd5rWG%%!oKW$NhDIas0|PiuJt4rd*6>w@-dj
zoM-!e_RZq+uiu>1eJpWobNY^Xwwm7VwyA8JR!UBi+Ix8E)FVlYH7s8I@7|Vw^Xk?k
z`!34J#y;irvD9|+$={N4g>QP#6n6nnuTQLTM$0yMX_xn}Ss)Va_;!Wpk|$9UQ#QQz
zN|+h3XO6Pqr;Lz?_qIlz*_xL8yI$kwszt9AH~)M7<H(Qlj~ipzPurbh4BfY(VO65U
zXA_$znI5*DY@J8VYtGI)l(bmN;=-9llR9#lmF_!C&;Oy`&ED$ti8t&2vg+lh)IamG
z<ZWpa{@K=j(%j`Gn_qwE5^os|YxMx#DHa!aU8ir;*O;BDBi6UjOE~7RNXDj()s36$
zZ8F!~y5ky^_FCwYWGD01fJY1TRvwyi?8BugZ6`Q7dJcr9u<)H#NUaE-uA01HmfIh`
zYa29m)Y2wQmXMJ)cK@lfYgbEwu4JQy;(^tir{{gV(eiOos{XAuQ>9n(?0+j|50pIo
z?L664a^@8tXT#sjzJ2!&?knQz?mRi^=R^+a&93z)17e#Rd<)ji<KUkC#JSG%+{`ES
zF1Pu2URbQ;c77Y9;Ns=o7wcWmiynOuDzx~z|8X$o+VgKy(ih8WrYBC-Ojn-B@3^MZ
z8=5rbUEksZj~c7pB|RVB-}|^j@y3?D&oe6K1SYK))7p5Qjcd;u6(+HVCHtHOmFArD
zwy^D6R$RY0n$L&d*QMomf`xnk(LK`|%HufqM6F<nGcQ~EMr((NL9gngCv&8iyB#_C
zdDY{mPFFv!G%By)%rU+Cv9M&W^x3L}LmJyT4nICV)u!L>_>@4S(3hJhZC>u2Hg$K@
zic@=Y1m^}nUMIGy(41}3zS_pm?=p1+qr?|}_2ylBy+UoOK>dajb0_@&-~8sL%y-XJ
zb*;>l_U5vxcIHOu%%pZ_<xT2OcI()Fd30F!q^ahl-7>Osz3oo$=X0w@q};smxA8hh
z=9;eWeiPm=IJe+l@Io~i<sJ4@k0ovVabaDiKdbKMQqGk>=5c+pHB5N_n9=$U&y9y=
z2h6f=yOtSs*06tN&E4l&Km7r_mFotj+egYB{8MIEoHToybFbsg2A)lar!ZD$Drna_
z$M0|M|LT!wxbg6w<N7P!=%jAhs;3xJ*{5wZ!RUlTm&uF`vwD4+k4A3z9@2kc)&6NV
z>4!a1)I=xDe6r%wg99RFxtlrsc@CPl6j-aTxD-3FH`u|fZ?UH0rZ^?dqDA%5u0bkO
zMOMDKq28OLdC@OlQ?WqVH%D{lh9w@!eYLl|WAEhF9$X~ZeT93Ey;|?lBeqMTyQP9&
zu`a3RUu}1NV$s9c=@Vw8>2#%PWC?Gvka%@zulK}gft{_&ZIgo7PtRKC_4liF1pD%h
zGbB!_<@PR2TIQx0<L%`*N4RvanlH=0`s|*cg?p#EKF+w#F1`7%pz|Xg^}b~bIres6
zu)dwQ_r`_l)a90$&wRi8y`RswH+jcJDZiF``f6YJ?)3*0{6Dy0{txE)6XqY&GQ4%{
zGo!!M)6J`Ye1C0`^kNoY#jVvR<?~J(c1?SvK7S#rT}1-FcguyJedZkkR{Dx1=lGj1
zRm{(=4^iuiZoPkJh1r?_yTe(5%R1O<uU=UC)%uRGuR^DS<#V^yj*dlsS1+Wtoha>l
zqb_WHRM?r9cjDo!+s0=mX=FaX{^P)VjYqGZm#nrp@p2c-nfga6bsWd?`#vA#ooK)H
z*bDP=qb>h8Z_>VXc)?^_o?kC*d06t6q`yh5ke@xL-|A%j35maNTdRu_UGxH$M)h4w
zH$1atO4zMCEiT^YE980G-%3ur!YP)zy*k?LplNyLnyzxsBR<FbBaFFoy8~2e7v#TS
zU;p%SgwHk6EupJ+CFR8@?7AHBb*-hw%(dPgN4L8kSv>LAlx4e?R2@^c_xV)##qHX)
z^VTx1;ai>fnGWW>>8robB!6S-F`=6Z`<Szpq>p%;X}tHm-EKDZni+4(*(sqX72X>~
zbT8d7<Hi-Ww+j}4qpxzE=6Shw_EJBx7EYO*_NtRbs3JXstGie2p@V~XE`#*M$WzsS
zJc<N&{o5h==?cH9;jdkb_@4ZbjrlPBn3nF9x!=#ccYf@-dONSDxoG{}fRNe+2c|P<
z>Zl!?T=cqytG;D^)Ekdbv$QFb*6un|c+T1Xj1#}DW_-hQn{P*K(!4b-uRc9e?0ew!
z851$-qlX%|iFX=p<L$6HrRMv|$j$MBROFAfcU-uxd=5HjH;b{Y=Q+ofWkFNdPM@Bd
zknCxhxJ*d$`I@BbY@Nq`efjNfUazLA!`JpCXra7bAJ^-Pdl(L$XXoxZaU=Na=9}^v
zn>N+8>&buHaH>ZxMR)QF2_L&Tyzh=Lxft=o@c6WS>{1gZ+h|Mkp5Qe8ZOpai`mcfu
zd$Jzy4G@<+8qyr0_2tDy+bz#lpS;7G5cX-l7sDwBy@DIR<)pf=DpY6$`CP1SUbSm#
zeR1t!ZsXt1!djMElHC2?ItyLbt`sPk$+XVaEoi~DFPooQ^k4hoK9zMjo2G@SpZFZ1
z9TN+eJh-C3x;$;sgDEi*GZ#IWA{^}(TCnPEjo;3IgDcmm{99mF#lQMhQ}Ek2O~J9g
zGVu)S3cV9Io_H^LJ*2?vbWW_35bN>~*5#k{G*{G%Xjz1Y-h458OOao4XH)RL*SRhS
zHQ%!?Z#SD77OTB$*^E_suJ02{=N?K*3Z5NO;C0le*GYhN`OI$x;uEHGi_VcMnr~G-
z%}vX<zr)z?M#M3<@I`9j%WsQhPqYfT+al+k9u}8dm0_7Vb(>1_>VF(M!rC{_oC=EC
zw(3PypzEyYde^E9h0N2JO=}nA#V2sjJf!8D*fBZ6<x@-P_D|DnEMpU-{;Tg;`y;ri
z`TdOGgtAtX4-03Cu4Gepos*+upnm3x&g0Xoa<o>*^d|8YOzKKDX17ROmZunhO+{m6
z8<!oskDu4+8D|*dW~_`ncIac?vd6loE=lP<xD_6DVAGcMFLu^n+hj6%%F>z3PhaA=
zbHXNQTH2&dTO1eZ^u|c=x<pE;FPpqd$JcJVj;~Dym(Qn0Y0sG{PyUsZ%#&z3`ykCG
zL+hT{$rYzg2e7fO`gtj0(W&nDG22#rdbIRZ_vdz>nZBzoE=qE<N$EN%b9VLNQyU}F
zuO+^$U3<WgtvX_nP<Qd`t1s%kbY_OCihb$ec55$wdc&+w&aNckwubivwuDz~rg{f8
zX0H0p7+U!?Bx_;Ss!gsXex_4Tt()aF-Q?zteFsFRXX*s{swVwYdKO=7diCTIhvwBQ
zef+p4+_(|zH$(7hh<K=PYSAReBt)pJ*gGpLpqw$Nv+k;+*`~a>$e$8StZ5#fPA!Wq
zTJOoeSEu$(&&_$#0%uC)*?ufC<$T7`y1Tpo-jmYrxsQ6oxu3Mn;Cs`&^I`jccF;D>
zj4$G?uf!P`j_qM&2=HcP5@A3b(J*<w4fGU-L|duJ?`<?#KvNBqK^#+CIL8ShdfHBE
z@<CfI2&d6bYBHyt4uk`e3ATgFxWPnSAPix9PY5Hy-i#My0er*%<i+-y@*ol9y`-Rx
z{;-)228N8uuk9rxL28iq8}T3%FfcH<@GvkGr<Rmt=B0xV2SE2GXg?BYD-sAxpy&*m
z9P6O205W~Xy?X(Mj0_AqEDQ{yC~9WvPWDw1pS;_F2doe|JV6sN2(`TWlb<`tvKhF8
FgaGwjF?av~

delta 14467
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
GNE86&#CL4~

-- 
GitLab