From a4c9e800ce2d59bbd93931251fc9106f800ba35a Mon Sep 17 00:00:00 2001
From: Markus Holzer <markus.holzer@fau.de>
Date: Fri, 22 Jul 2022 13:06:53 +0200
Subject: [PATCH] Add drop impact and Taylor bubble showcase for phase-field
 LBM

---
 .../PhaseFieldAllenCahn/CPU/CMakeLists.txt    |   2 +-
 .../CPU/InitializerFunctions.cpp              | 278 +++++++++++++-----
 .../CPU/InitializerFunctions.h                |  16 +-
 .../PhaseFieldAllenCahn/CPU/multiphase.cpp    |  97 +++++-
 .../CPU/multiphase_RTI_3D.py                  |   2 +-
 .../CPU/multiphase_codegen.py                 |   5 +-
 .../CPU/multiphase_drop.py                    | 108 +++++++
 .../CPU/multiphase_rising_bubble.py           |   2 +-
 .../CPU/multiphase_taylor_bubble.py           | 184 ++++++++++++
 .../PhaseFieldAllenCahn/GPU/CMakeLists.txt    |   2 +-
 10 files changed, 605 insertions(+), 91 deletions(-)
 create mode 100755 apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_drop.py
 create mode 100755 apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_taylor_bubble.py

diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/CMakeLists.txt b/apps/showcases/PhaseFieldAllenCahn/CPU/CMakeLists.txt
index abce2c997..383c9d31a 100644
--- a/apps/showcases/PhaseFieldAllenCahn/CPU/CMakeLists.txt
+++ b/apps/showcases/PhaseFieldAllenCahn/CPU/CMakeLists.txt
@@ -18,4 +18,4 @@ waLBerla_generate_target_from_python(NAME PhaseFieldCodeGenCPU
 
 waLBerla_add_executable(NAME multiphaseCPU
         FILES multiphase.cpp PythonExports.cpp InitializerFunctions.cpp multiphase_codegen.py
-        DEPENDS blockforest core field postprocessing python_coupling lbm geometry timeloop gui PhaseFieldCodeGenCPU)
+        DEPENDS blockforest core field postprocessing python_coupling lbm geometry timeloop PhaseFieldCodeGenCPU)
diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/InitializerFunctions.cpp b/apps/showcases/PhaseFieldAllenCahn/CPU/InitializerFunctions.cpp
index d7cb146a0..da5d24f34 100644
--- a/apps/showcases/PhaseFieldAllenCahn/CPU/InitializerFunctions.cpp
+++ b/apps/showcases/PhaseFieldAllenCahn/CPU/InitializerFunctions.cpp
@@ -23,38 +23,82 @@
 
 #include "field/FlagField.h"
 #include "field/communication/PackInfo.h"
-#include "field/vtk/VTKWriter.h"
-
-#include "python_coupling/DictWrapper.h"
 
 namespace walberla
 {
-using PhaseField_T = GhostLayerField< real_t, 1 >;
-using FlagField_T  = FlagField< uint8_t >;
+using PhaseField_T    = GhostLayerField< real_t, 1 >;
+using FlagField_T     = FlagField< uint8_t >;
+using VelocityField_T = GhostLayerField< real_t, 3 >;
 
 void initPhaseField_sphere(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID phaseFieldID,
-                           const real_t R                         = real_c(10.0),
+                           BlockDataID velocityFieldID, const real_t R = real_c(10.0),
                            const Vector3< real_t > bubbleMidPoint = Vector3< real_t >(0.0, 0.0, 0.0),
-                           const bool bubble = true, const real_t W = real_c(5.0))
+                           const bool bubble = true, const real_t W = real_c(5.0),
+                           const Vector3< real_t >& initialVelocity = Vector3< real_t >(real_c(0)))
+{
+   for (auto& block : *blocks)
+   {
+      auto phaseField    = block.getData< PhaseField_T >(phaseFieldID);
+      auto velocityField = block.getData< VelocityField_T >(velocityFieldID);
+      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(phaseField, {
+         Cell globalCell;
+         blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
+         real_t Ri =
+            real_c(sqrt((real_c(globalCell[0]) - bubbleMidPoint[0]) * (real_c(globalCell[0]) - bubbleMidPoint[0]) +
+                        (real_c(globalCell[1]) - bubbleMidPoint[1]) * (real_c(globalCell[1]) - bubbleMidPoint[1]) +
+                        (real_c(globalCell[2]) - bubbleMidPoint[2]) * (real_c(globalCell[2]) - bubbleMidPoint[2])));
+         if (bubble) { phaseField->get(x, y, z) = real_c(0.5) + real_c(0.5) * real_c(tanh(2.0 * (Ri - R) / W)); }
+         else
+         {
+            phaseField->get(x, y, z) = real_c(0.5) - real_c(0.5) * real_c(tanh(2.0 * (Ri - R) / W));
+
+            if (Ri < R + W)
+            {
+               velocityField->get(x, y, z, 0) = initialVelocity[0];
+               velocityField->get(x, y, z, 1) = initialVelocity[1];
+               velocityField->get(x, y, z, 2) = initialVelocity[2];
+            }
+         }
+      }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
+   }
+}
+
+void initPhaseField_pool(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID phaseFieldID, real_t W,
+                         real_t poolDepth)
 {
    for (auto& block : *blocks)
    {
       auto phaseField = block.getData< PhaseField_T >(phaseFieldID);
-      // clang-format off
-      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(phaseField, Cell globalCell;
+
+      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(phaseField, {
+         Cell globalCell;
+         blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
+
+         phaseField->get(x, y, z) +=
+            real_c(0.5) - real_c(0.5) * real_c(tanh(real_c(2.0) * (real_c(globalCell[1]) - poolDepth) / W));
+         if (phaseField->get(x, y, z) > real_c(1)) { phaseField->get(x, y, z) = real_c(1); }
+      }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
+   }
+}
+
+void init_hydrostatic_pressure(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID densityFieldID,
+                               real_t GravitationalAcceleration, real_t poolDepth)
+{
+   for (auto& block : *blocks)
+   {
+      auto densityField = block.getData< PhaseField_T >(densityFieldID);
+      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(densityField, {
+         Cell globalCell;
          blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
-         real_t Ri = sqrt((real_c(globalCell[0]) - bubbleMidPoint[0]) * (real_c(globalCell[0]) - bubbleMidPoint[0]) +
-                          (real_c(globalCell[1]) - bubbleMidPoint[1]) * (real_c(globalCell[1]) - bubbleMidPoint[1]) +
-                          (real_c(globalCell[2]) - bubbleMidPoint[2]) * (real_c(globalCell[2]) - bubbleMidPoint[2]));
-         if (bubble) phaseField->get(x, y, z) = real_c(0.5) + real_c(0.5) * real_c(tanh(2.0 * (Ri - R) / W));
-         else phaseField->get(x, y, z)        = real_c(0.5) - real_c(0.5) * real_c(tanh(2.0 * (Ri - R) / W));
-      )
-      // clang-format on
+         real_t pressure = real_c(3.0) * GravitationalAcceleration * (real_c(globalCell[1]) - poolDepth);
+         densityField->get(x, y, z) += pressure;
+      }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
    }
 }
 
 void init_Taylor_bubble(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID phaseFieldID,
-                        const real_t D = real_c(5.0), const real_t H = real_c(2.0), const real_t DT = real_c(20.0), const real_t Donut_x0 = real_c(40.0))
+                        const real_t D = real_c(5.0), const real_t H = real_c(2.0), const real_t DT = real_c(20.0),
+                        const real_t Donut_x0 = real_c(40.0))
 {
    auto Mx = real_c(blocks->getDomainCellBB().xMax()) / real_c(2.0);
    auto Mz = real_c(blocks->getDomainCellBB().zMax()) / real_c(2.0);
@@ -62,16 +106,66 @@ void init_Taylor_bubble(const shared_ptr< StructuredBlockStorage >& blocks, Bloc
    for (auto& block : *blocks)
    {
       auto phaseField = block.getData< PhaseField_T >(phaseFieldID);
-      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(
-         phaseField, Cell globalCell; blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
+      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(phaseField, {
+         Cell globalCell;
+         blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
 
-         real_t Ri = D * real_c(sqrt(pow(H, 2) - pow(DT - sqrt(pow(real_c(globalCell[0]) - Mx, 2) + pow(real_c(globalCell[2]) - Mz, 2)), 2)));
+         real_t Ri =
+            D *
+            real_c(sqrt(pow(H, 2) -
+                        pow(DT - sqrt(pow(real_c(globalCell[0]) - Mx, 2) + pow(real_c(globalCell[2]) - Mz, 2)), 2)));
 
-         real_t shifter           = atan2((real_c(globalCell[2]) - Mz), (real_c(globalCell[0]) - Mx));
+         real_t shifter = real_c(atan2((real_c(globalCell[2]) - Mz), (real_c(globalCell[0]) - Mx)));
          if (shifter < 0) shifter = shifter + 2 * math::pi;
-         if ((real_c(globalCell[1]) < Donut_x0 + Ri * sin(shifter / 2.0)) && (real_c(globalCell[1]) > Donut_x0 - Ri)) {
+         if ((real_c(globalCell[1]) < Donut_x0 + Ri * real_c(sin(shifter / 2.0))) && (real_c(globalCell[1]) > Donut_x0 - Ri))
+         {
             phaseField->get(x, y, z) = 0.0;
-         } else { phaseField->get(x, y, z) = real_c(1.0); })
+         }
+         else { phaseField->get(x, y, z) = real_c(1.0); }
+      }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
+   }
+}
+
+void init_Taylor_bubble_cylindric(const shared_ptr< StructuredBlockStorage >& blocks, const BlockDataID& phaseFieldID,
+                                  real_t R, real_t H, real_t L, real_t W)
+{
+   auto Mx = real_c(blocks->getDomainCellBB().xMax()) / real_c(2.0);
+   auto Mz = real_c(blocks->getDomainCellBB().zMax()) / real_c(2.0);
+
+   for (auto& block : *blocks)
+   {
+      PhaseField_T* phaseField = block.getData< PhaseField_T >(phaseFieldID);
+
+      // initialize top and bottom walls of cylinder
+      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(phaseField, {
+         Cell globalCell;
+         blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
+
+         const real_t Ri = real_c(sqrt(pow(real_c(globalCell[0]) - Mx, 2) + pow(real_c(globalCell[2]) - Mz, 2)));
+
+         const real_t cylinderMidY = real_c(H) + real_c(0.5) * real_c(L);
+
+         const real_t distance = std::abs(real_c(globalCell[1]) - cylinderMidY);
+         const real_t lHalf    = real_c(0.5) * real_c(L);
+
+         // cylinder side walls
+         if (real_c(globalCell[1]) >= H - W && real_c(globalCell[1]) < H + L + W)
+         {
+            phaseField->get(x, y, z) = real_c(0.5) + real_c(0.5) * real_c(tanh(real_c(2.0) * (Ri - R) / W));
+
+            // cylinder top and bottom walls (must be added to not overwrite the side wall initialization)
+            if (Ri < R + W)
+            {
+               phaseField->get(x, y, z) +=
+                  real_c(0.5) + real_c(0.5) * real_c(tanh(real_c(2.0) * (distance - lHalf) / W));
+
+               // limit maximum values of phase field
+               if (phaseField->get(x, y, z) > real_c(1)) { phaseField->get(x, y, z) = real_c(1); }
+               if (phaseField->get(x, y, z) < real_c(0)) { phaseField->get(x, y, z) = real_c(0); }
+            }
+         }
+         else { phaseField->get(x, y, z) = real_c(1); }
+      }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
    }
 }
 
@@ -92,8 +186,8 @@ void init_bubble_field(const shared_ptr< StructuredBlockStorage >& blocks, Block
 
    // distance in between the bubbles
    real_t dist = real_c(4.0);
-   auto nx  = uint_c(floor(X / (D + dist * W)));
-   auto nz  = uint_c(floor(Z / (D + dist * W)));
+   auto nx     = uint_c(floor(X / (D + dist * W)));
+   auto nz     = uint_c(floor(Z / (D + dist * W)));
 
    // fluctuation of the bubble radii
    std::vector< std::vector< real_t > > fluctuation_radius(nx, std::vector< real_t >(nz, 0.0));
@@ -113,39 +207,50 @@ void init_bubble_field(const shared_ptr< StructuredBlockStorage >& blocks, Block
    for (auto& block : *blocks)
    {
       auto phaseField = block.getData< PhaseField_T >(phaseFieldID);
-      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(
-         phaseField, Cell globalCell; blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
-         for (unsigned int i = 0; i < nx; ++i) {
+      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(phaseField, {
+         Cell globalCell;
+         blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
+         for (unsigned int i = 0; i < nx; ++i)
+         {
             for (unsigned int j = 0; j < nz; ++j)
             {
-               bubbleMidPoint[0] = real_c(i + 1) * (D + (dist * W)) - (D + (dist * W)) / real_c(2.0) + fluctuation_pos[i][j];
+               bubbleMidPoint[0] =
+                  real_c(i + 1) * (D + (dist * W)) - (D + (dist * W)) / real_c(2.0) + fluctuation_pos[i][j];
                bubbleMidPoint[1] = R + W + 4;
-               bubbleMidPoint[2] = real_c(j + 1) * (D + (dist * W)) - (D + (dist * W)) / real_c(2.0) + fluctuation_pos[i][j];
+               bubbleMidPoint[2] =
+                  real_c(j + 1) * (D + (dist * W)) - (D + (dist * W)) / real_c(2.0) + fluctuation_pos[i][j];
 
-               real_t Ri = sqrt((real_c(globalCell[0]) - bubbleMidPoint[0]) * (real_c(globalCell[0]) - bubbleMidPoint[0]) +
-                                (real_c(globalCell[1]) - bubbleMidPoint[1]) * (real_c(globalCell[1]) - bubbleMidPoint[1]) +
-                                (real_c(globalCell[2]) - bubbleMidPoint[2]) * (real_c(globalCell[2]) - bubbleMidPoint[2]));
-               if (real_c(globalCell[0]) >= real_c(i) * (D + dist * W) && real_c(globalCell[0]) <= real_c(i + 1) * (D + dist * W) &&
-                   real_c(globalCell[2]) >= real_c(j) * (D + dist * W) && real_c(globalCell[2]) <= real_c(j + 1) * (D + dist * W))
-                  phaseField->get(x, y, z) = real_c(0.5) + real_c(0.5) * tanh(real_c(2.0) * (Ri - (R - fluctuation_radius[i][j])) / W);
+               real_t Ri = real_c(
+                  sqrt((real_c(globalCell[0]) - bubbleMidPoint[0]) * (real_c(globalCell[0]) - bubbleMidPoint[0]) +
+                       (real_c(globalCell[1]) - bubbleMidPoint[1]) * (real_c(globalCell[1]) - bubbleMidPoint[1]) +
+                       (real_c(globalCell[2]) - bubbleMidPoint[2]) * (real_c(globalCell[2]) - bubbleMidPoint[2])));
+               if (real_c(globalCell[0]) >= real_c(i) * (D + dist * W) &&
+                   real_c(globalCell[0]) <= real_c(i + 1) * (D + dist * W) &&
+                   real_c(globalCell[2]) >= real_c(j) * (D + dist * W) &&
+                   real_c(globalCell[2]) <= real_c(j + 1) * (D + dist * W))
+                  phaseField->get(x, y, z) =
+                     real_c(0.5) + real_c(0.5) * real_c(tanh(real_c(2.0) * (Ri - (R - fluctuation_radius[i][j])) / W));
 
                if (real_c(globalCell[0]) > real_c(nx) * (D + dist * W)) phaseField->get(x, y, z) = real_c(1.0);
                if (real_c(globalCell[2]) > real_c(nz) * (D + dist * W)) phaseField->get(x, y, z) = real_c(1.0);
             }
          }
 
-         if (real_c(globalCell[1]) > gas_top) {
-            phaseField->get(x, y, z) = real_c(0.5) + real_c(0.5) * tanh(real_c(2.0) * (gas_top + 10 - real_c(globalCell[1])) / W);
-         })
+         if (real_c(globalCell[1]) > gas_top)
+         {
+            phaseField->get(x, y, z) =
+               real_c(0.5) + real_c(0.5) * real_c(tanh(real_c(2.0) * (gas_top + 10 - real_c(globalCell[1])) / W));
+         }
+      }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
    }
 }
 
 void initPhaseField_RTI(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID phaseFieldID,
                         const real_t W = real_c(5.0), const bool pipe = true)
 {
-   auto X              = real_c(blocks->getDomainCellBB().xMax());
-   auto Z              = real_c(blocks->getDomainCellBB().zMax());
-   auto halfY          = real_c(blocks->getDomainCellBB().yMax()) / real_c(2.0);
+   auto X            = real_c(blocks->getDomainCellBB().xMax());
+   auto Z            = real_c(blocks->getDomainCellBB().zMax());
+   auto halfY        = real_c(blocks->getDomainCellBB().yMax()) / real_c(2.0);
    auto perturbation = real_c(0.05);
 
    if (pipe)
@@ -153,12 +258,16 @@ void initPhaseField_RTI(const shared_ptr< StructuredBlockStorage >& blocks, Bloc
       for (auto& block : *blocks)
       {
          auto phaseField = block.getData< PhaseField_T >(phaseFieldID);
-         WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(
-            phaseField, Cell globalCell; blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
-            real_t R     = sqrt((real_c(globalCell[0]) - X / 2) * (real_c(globalCell[0]) - X / 2) +
-                            (real_c(globalCell[2]) - Z / 2) * (real_c(globalCell[2]) - Z / 2));
-            if (R > X) R = X; real_t tmp = perturbation * X * cos((real_c(2.0) * math::pi * R) / X);
-            phaseField->get(x, y, z)     = real_c(0.5) + real_c(0.5) * tanh(((real_c(globalCell[1]) - halfY) + tmp) / (W / real_c(2.0)));)
+         WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(phaseField, {
+            Cell globalCell;
+            blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
+            real_t R = real_c(sqrt((real_c(globalCell[0]) - X / 2) * (real_c(globalCell[0]) - X / 2) +
+                            (real_c(globalCell[2]) - Z / 2) * (real_c(globalCell[2]) - Z / 2)));
+            if (R > X) R = X;
+            real_t tmp = perturbation * X * real_c(cos((real_c(2.0) * math::pi * R) / X));
+            phaseField->get(x, y, z) =
+               real_c(0.5) + real_c(0.5) * real_c(tanh(((real_c(globalCell[1]) - halfY) + tmp) / (W / real_c(2.0))));
+         }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
       }
    }
    else
@@ -166,11 +275,15 @@ void initPhaseField_RTI(const shared_ptr< StructuredBlockStorage >& blocks, Bloc
       for (auto& block : *blocks)
       {
          auto phaseField = block.getData< PhaseField_T >(phaseFieldID);
-         WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(
-            phaseField, Cell globalCell; blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
+         WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(phaseField, {
+            Cell globalCell;
+            blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
             real_t tmp = perturbation * X *
-                         (cos((real_c(2.0) * math::pi * real_c(globalCell[0])) / X) + cos((real_c(2.0) * math::pi * real_c(globalCell[2])) / X));
-            phaseField->get(x, y, z) = real_c(0.5) + real_c(0.5) * tanh(((real_c(globalCell[1]) - halfY) - tmp) / (W / real_c(2.0)));)
+                         (real_c(cos((real_c(2.0) * math::pi * real_c(globalCell[0])) / X)) +
+                          real_c(cos((real_c(2.0) * math::pi * real_c(globalCell[2])) / X)));
+            phaseField->get(x, y, z) =
+               real_c(0.5) + real_c(0.5) * real_c(tanh(((real_c(globalCell[1]) - halfY) - tmp) / (W / real_c(2.0))));
+         }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
       }
    }
 }
@@ -193,31 +306,41 @@ void initTubeWithCylinder(const shared_ptr< StructuredBlockStorage >& blocks, Bl
       {
          auto flagField    = block.template getData< FlagField_T >(flagFieldID);
          auto boundaryFlag = flagField->getOrRegisterFlag(boundaryFlagUID);
-         WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(flagField, Cell globalCell; 
+         WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(flagField, {
+            Cell globalCell;
             blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
             real_t R1;
-            if (real_c(globalCell[1]) <= start_transition) {
-               R1 = sqrt((real_c(globalCell[0]) - Mx) * (real_c(globalCell[0]) - Mx) + (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz));
-            } else if (real_c(globalCell[1]) > start_transition && real_c(globalCell[1]) < start_transition + length_transition) {
+            if (real_c(globalCell[1]) <= start_transition)
+            {
+               R1 = real_c(sqrt((real_c(globalCell[0]) - Mx) * (real_c(globalCell[0]) - Mx) +
+                         (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz)));
+            }
+            else if (real_c(globalCell[1]) > start_transition &&
+                     real_c(globalCell[1]) < start_transition + length_transition)
+            {
                real_t tmp       = math::pi * (real_c(globalCell[1]) - start_transition) / (length_transition);
-               real_t shift_tmp = shift * real_c(0.5) * (1 - cos(tmp));
-               R1               = sqrt((real_c(globalCell[0]) - Mx - shift_tmp) * (real_c(globalCell[0]) - Mx - shift_tmp) +
-                         (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz));
-            } else {
-               R1 = sqrt((real_c(globalCell[0]) - Mx - shift) * (real_c(globalCell[0]) - Mx - shift) +
-                         (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz));
+               real_t shift_tmp = shift * real_c(0.5) * (1 - real_c(cos(tmp)));
+               R1 = real_c(sqrt((real_c(globalCell[0]) - Mx - shift_tmp) * (real_c(globalCell[0]) - Mx - shift_tmp) +
+                         (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz)));
+            }
+            else
+            {
+               R1 = real_c(sqrt((real_c(globalCell[0]) - Mx - shift) * (real_c(globalCell[0]) - Mx - shift) +
+                         (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz)));
             }
 
-            real_t R2 = sqrt((real_c(globalCell[0]) - Mx) * (real_c(globalCell[0]) - Mx) + (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz));
+            real_t R2 = real_c(sqrt((real_c(globalCell[0]) - Mx) * (real_c(globalCell[0]) - Mx) +
+                             (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz)));
             if (R1 < R_in) addFlag(flagField->get(x, y, z), boundaryFlag);
-            if (R2 > R_outer) addFlag(flagField->get(x, y, z), boundaryFlag);)
+            if (R2 > R_outer) addFlag(flagField->get(x, y, z), boundaryFlag);
+         }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
       }
    }
    else
    {
       auto Mx = real_c(blocks->getDomainCellBB().xMax()) / real_c(2.0);
       auto Mz = real_c(blocks->getDomainCellBB().zMax()) / real_c(2.0);
-      
+
       auto R_outer = real_c(blocks->getDomainCellBB().xMax()) / real_c(2.0) + real_c(1.0);
 
       auto const shift = real_c(eccentricity * R_in);
@@ -227,21 +350,24 @@ void initTubeWithCylinder(const shared_ptr< StructuredBlockStorage >& blocks, Bl
       {
          auto flagField    = block.template getData< FlagField_T >(flagFieldID);
          auto boundaryFlag = flagField->getOrRegisterFlag(boundaryFlagUID);
-         WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(
-            flagField, Cell globalCell; blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
-            if (real_c(globalCell[1]) <= start_transition) {
-               R_tmp = R_in;
-            } else if (real_c(globalCell[1]) > start_transition && real_c(globalCell[1]) < start_transition + length_transition) {
+         WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(flagField, {
+            Cell globalCell;
+            blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
+            if (real_c(globalCell[1]) <= start_transition) { R_tmp = R_in; }
+            else if (real_c(globalCell[1]) > start_transition &&
+                     real_c(globalCell[1]) < start_transition + length_transition)
+            {
                real_t tmp       = math::pi * (real_c(globalCell[1]) - start_transition) / (length_transition);
-               real_t shift_tmp = shift * real_c(0.5) * (1 - cos(tmp));
-               R_tmp = R_in + shift_tmp;
-            } else {
-               R_tmp = R_in + shift;
+               real_t shift_tmp = shift * real_c(0.5) * (1 - real_c(cos(tmp)));
+               R_tmp            = R_in + shift_tmp;
             }
+            else { R_tmp = R_in + shift; }
 
-            real_t R2 = sqrt((real_c(globalCell[0]) - Mx) * (real_c(globalCell[0]) - Mx) + (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz));
+            real_t R2 = real_c(sqrt((real_c(globalCell[0]) - Mx) * (real_c(globalCell[0]) - Mx) +
+                             (real_c(globalCell[2]) - Mz) * (real_c(globalCell[2]) - Mz)));
             if (R2 < R_tmp) addFlag(flagField->get(x, y, z), boundaryFlag);
-            if (R2 > R_outer) addFlag(flagField->get(x, y, z), boundaryFlag);)
+            if (R2 > R_outer) addFlag(flagField->get(x, y, z), boundaryFlag);
+         }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
       }
    }
 }
diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/InitializerFunctions.h b/apps/showcases/PhaseFieldAllenCahn/CPU/InitializerFunctions.h
index 585639ac9..ae99d3ab0 100644
--- a/apps/showcases/PhaseFieldAllenCahn/CPU/InitializerFunctions.h
+++ b/apps/showcases/PhaseFieldAllenCahn/CPU/InitializerFunctions.h
@@ -31,12 +31,24 @@
 
 namespace walberla
 {
-void initPhaseField_sphere(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID phaseFieldID, real_t R,
-                           Vector3< real_t > bubbleMidPoint, bool bubble = true, real_t W = 5);
+void initPhaseField_sphere(const shared_ptr< StructuredBlockStorage >& blocks,
+                           BlockDataID phaseFieldID, BlockDataID velocityFieldID,
+                           real_t R,
+                           Vector3< real_t > bubbleMidPoint, bool bubble = true, real_t W = 5,
+                           const Vector3< real_t >& initialVelocity = Vector3< real_t >(real_c(0)));
+
+void initPhaseField_pool(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID phaseFieldID, real_t W,
+                         real_t poolDepth);
+
+void init_hydrostatic_pressure(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID densityFieldID,
+                               real_t GravitationalAcceleration, real_t poolDepth);
 
 void init_Taylor_bubble(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID phaseFieldID, real_t D = 5,
                         real_t H = 2, real_t DT = 20, real_t Donut_x0 = 40);
 
+void init_Taylor_bubble_cylindric(const shared_ptr< StructuredBlockStorage >& blocks, const BlockDataID& phaseFieldID,
+                                  real_t R, real_t H, real_t L, real_t W);
+
 void init_bubble_field(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID phaseFieldID, real_t R,
                        real_t W = 5);
 
diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase.cpp b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase.cpp
index 8e0f2cb62..2ed8706f3 100644
--- a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase.cpp
+++ b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase.cpp
@@ -30,9 +30,12 @@
 #include "field/vtk/VTKWriter.h"
 
 #include "geometry/InitBoundaryHandling.h"
+#include "geometry/mesh/TriangleMeshIO.h"
 
 #include "lbm/PerformanceEvaluation.h"
 
+#include "postprocessing/FieldToSurfaceMesh.h"
+
 #include "python_coupling/CreateConfig.h"
 #include "python_coupling/PythonCallback.h"
 
@@ -68,8 +71,9 @@ int main(int argc, char** argv)
       // ADD DOMAIN PARAMETERS //
       //////////////////////////
 
-      auto domainSetup                = config->getOneBlock("DomainSetup");
-      const bool tube                 = domainSetup.getParameter< bool >("tube", true);
+      auto domainSetup             = config->getOneBlock("DomainSetup");
+      Vector3< uint_t > domainSize = domainSetup.getParameter< Vector3< uint_t > >("domainSize");
+      const bool tube              = domainSetup.getParameter< bool >("tube", false);
 
       ////////////////////////////////////////
       // ADD GENERAL SIMULATION PARAMETERS //
@@ -92,7 +96,8 @@ int main(int argc, char** argv)
          field::addToStorage< PdfField_hydro_T >(blocks, "LB velocity field", real_c(0.0), field::fzyx);
       BlockDataID velocity_field_ID =
          field::addToStorage< VelocityField_T >(blocks, "velocity", real_c(0.0), field::fzyx);
-      BlockDataID phase_field_ID = field::addToStorage< PhaseField_T >(blocks, "phase", real_c(0.0), field::fzyx);
+      BlockDataID density_field_ID = field::addToStorage< PhaseField_T >(blocks, "density", real_c(1.0), field::fzyx);
+      BlockDataID phase_field_ID   = field::addToStorage< PhaseField_T >(blocks, "phase", real_c(0.0), field::fzyx);
 
       BlockDataID flagFieldID = field::addFlagFieldToStorage< FlagField_T >(blocks, "flag field");
 
@@ -108,15 +113,45 @@ int main(int argc, char** argv)
       const real_t interface_thickness    = physical_parameters.getParameter< real_t >("interface_thickness");
 
       WALBERLA_LOG_INFO_ON_ROOT("Initialisation of the phase-field")
-      if (scenario == 1)
+      switch (scenario)
       {
+      case 1: {
          auto bubbleParameters                  = config->getOneBlock("Bubble");
          const Vector3< real_t > bubbleMidPoint = bubbleParameters.getParameter< Vector3< real_t > >("bubbleMidPoint");
          const real_t bubbleRadius              = bubbleParameters.getParameter< real_t >("bubbleRadius", real_c(20.0));
          const bool bubble                      = bubbleParameters.getParameter< bool >("bubble", true);
-         initPhaseField_sphere(blocks, phase_field_ID, bubbleRadius, bubbleMidPoint, bubble);
+         initPhaseField_sphere(blocks, phase_field_ID, velocity_field_ID, bubbleRadius, bubbleMidPoint, bubble);
+         break;
+      }
+      case 2: {
+         initPhaseField_RTI(blocks, phase_field_ID, interface_thickness, tube);
+         break;
+      }
+      case 3: {
+         auto dropParameters                     = config->getOneBlock("Drop");
+         const Vector3< real_t > dropMidPoint    = dropParameters.getParameter< Vector3< real_t > >("drop_mid_point");
+         const real_t dropRadius                 = dropParameters.getParameter< real_t >("drop_radius");
+         const real_t poolDepth                  = dropParameters.getParameter< real_t >("pool_depth");
+         const Vector3< real_t > impact_velocity = dropParameters.getParameter< Vector3< real_t > >("impact_velocity");
+         init_hydrostatic_pressure(blocks, density_field_ID, gravitational_acceleration, poolDepth);
+
+         initPhaseField_sphere(blocks, phase_field_ID, velocity_field_ID, dropRadius, dropMidPoint, false,
+                               interface_thickness, impact_velocity);
+         initPhaseField_pool(blocks, phase_field_ID, interface_thickness, poolDepth);
+         break;
+      }
+      case 4: {
+         auto TaylorBubbleParameters = config->getOneBlock("TaylorBubble");
+         const real_t BubbleRadius   = TaylorBubbleParameters.getParameter< real_t >("bubble_radius");
+         const real_t InitialHeight  = TaylorBubbleParameters.getParameter< real_t >("initial_height");
+         const real_t Length         = TaylorBubbleParameters.getParameter< real_t >("length");
+         init_Taylor_bubble_cylindric(blocks, phase_field_ID, BubbleRadius, InitialHeight, Length,
+                                      real_c(interface_thickness));
+         break;
+      }
+      default:
+         WALBERLA_ABORT("Scenario is not defined.")
       }
-      else if (scenario == 2) { initPhaseField_RTI(blocks, phase_field_ID, interface_thickness, tube); }
       WALBERLA_LOG_INFO_ON_ROOT("Initialisation of the phase-field done")
 
       /////////////////
@@ -125,7 +160,8 @@ int main(int argc, char** argv)
 
       pystencils::initialize_phase_field_distributions init_h(allen_cahn_PDFs_ID, phase_field_ID, velocity_field_ID,
                                                               interface_thickness);
-      pystencils::initialize_velocity_based_distributions init_g(hydrodynamic_PDFs_ID, velocity_field_ID);
+      pystencils::initialize_velocity_based_distributions init_g(density_field_ID, hydrodynamic_PDFs_ID,
+                                                                 velocity_field_ID);
 
       pystencils::phase_field_LB_step phase_field_LB_step(allen_cahn_PDFs_ID, phase_field_ID, velocity_field_ID,
                                                           mobility, interface_thickness);
@@ -180,6 +216,29 @@ int main(int argc, char** argv)
       if (boundariesConfig)
       {
          geometry::initBoundaryHandling< FlagField_T >(*blocks, flagFieldID, boundariesConfig);
+         if (tube)
+         {
+            // initialize cylindrical domain walls
+            const Vector3< real_t > domainCylinderBottomEnd =
+               Vector3< real_t >(real_c(domainSize[0]) * real_c(0.5), real_c(0), real_c(domainSize[2]) * real_c(0.5));
+            const Vector3< real_t > domainCylinderTopEnd = Vector3< real_t >(
+               real_c(domainSize[0]) * real_c(0.5), real_c(domainSize[1]), real_c(domainSize[2]) * real_c(0.5));
+            const real_t radius = real_c(domainSize[0]) * real_c(0.5);
+            const geometry::Cylinder cylinderTube(domainCylinderBottomEnd, domainCylinderTopEnd, radius);
+
+            for (auto blockIt = blocks->begin(); blockIt != blocks->end(); ++blockIt)
+            {
+               FlagField_T* flagField = blockIt->template getData< FlagField_T >(flagFieldID);
+               auto wallFlag          = flagField->getOrRegisterFlag(wallFlagUID);
+               WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(flagField, {
+                  Cell globalCell = Cell(x, y, z);
+                  blocks->transformBlockLocalToGlobalCell(globalCell, *blockIt);
+                  const Vector3< real_t > globalPoint = blocks->getCellCenter(globalCell);
+
+                  if (!geometry::contains(cylinderTube, globalPoint)) { addFlag(flagField->get(x, y, z), wallFlag); }
+               }) // WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ
+            }
+         }
          geometry::setNonBoundaryCellsToDomain< FlagField_T >(*blocks, flagFieldID, fluidFlagUID);
       }
 
@@ -248,6 +307,30 @@ int main(int argc, char** argv)
          },
          "Python callback");
 
+      int meshWriteFrequency = parameters.getParameter< int >("meshWriteFrequency", 0);
+      int counter            = 0;
+      int targetRank         = 0;
+      if (meshWriteFrequency > 0)
+      {
+         timeloop.addFuncAfterTimeStep(
+            [&]() {
+               if (timeloop.getCurrentTimeStep() % uint_t(meshWriteFrequency) == 0)
+               {
+                  auto mesh = postprocessing::realFieldToSurfaceMesh< PhaseField_T >(blocks, phase_field_ID, 0.5, 0,
+                                                                                     true, targetRank, MPI_COMM_WORLD);
+                  WALBERLA_EXCLUSIVE_WORLD_SECTION(targetRank)
+                  {
+                     std::string path = "";
+                     std::ostringstream out;
+                     out << std::internal << std::setfill('0') << std::setw(6) << counter;
+                     geometry::writeMesh(path + out.str() + ".obj", *mesh);
+                     counter++;
+                  }
+               }
+            },
+            "Mesh writer");
+      }
+
       // remaining time logger
       timeloop.addFuncAfterTimeStep(
          timing::RemainingTimeLogger(timeloop.getNrOfTimeSteps(), remainingTimeLoggerFrequency),
diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_RTI_3D.py b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_RTI_3D.py
index d7e2f1959..58ac2fbac 100755
--- a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_RTI_3D.py
+++ b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_RTI_3D.py
@@ -61,7 +61,7 @@ class Scenario:
         # everything else
         self.dbFile = "RTI.csv"
 
-        self.scenario = 2  # 1 rising bubble or droplet, 2 RTI
+        self.scenario = 2   # 1 rising bubble, 2 RTI, 3 drop, 4 taylor bubble set up
         self.config_dict = self.config()
 
     @wlb.member_callback
diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_codegen.py b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_codegen.py
index 1b5113454..0b117fc23 100644
--- a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_codegen.py
+++ b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_codegen.py
@@ -28,7 +28,7 @@ with CodeGeneration() as ctx:
     stencil_hydro = LBStencil(Stencil.D3Q27)
     assert (stencil_phase.D == stencil_hydro.D)
 
-    # In the codegneration skript we only need the symbolic parameters
+    # In the code-generation skript, we only need the symbolic parameters
     parameters = AllenCahnParameters(0, 0, 0, 0, 0)
 
     ########################
@@ -37,6 +37,7 @@ with CodeGeneration() as ctx:
 
     # velocity field
     u = fields(f"vel_field({stencil_hydro.D}): {field_type}[{stencil_hydro.D}D]", layout='fzyx')
+    density_field = fields(f"density_field: {field_type}[{stencil_hydro.D}D]", layout='fzyx')
     # phase-field
     C = fields(f"phase_field: {field_type}[{stencil_hydro.D}D]", layout='fzyx')
     C_tmp = fields(f"phase_field_tmp: {field_type}[{stencil_hydro.D}D]", layout='fzyx')
@@ -87,7 +88,7 @@ with CodeGeneration() as ctx:
 
     # create the kernels for the initialization of the g and h field
     h_updates = initializer_kernel_phase_field_lb(method_phase, C, u, h, parameters)
-    g_updates = initializer_kernel_hydro_lb(method_hydro, 1.0, u, g)
+    g_updates = initializer_kernel_hydro_lb(method_hydro, density_field, u, g)
 
     force_h = interface_tracking_force(C, stencil_phase, parameters)
     hydro_force = hydrodynamic_force(g, C, method_hydro, parameters, body_force)
diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_drop.py b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_drop.py
new file mode 100755
index 000000000..2845605b1
--- /dev/null
+++ b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_drop.py
@@ -0,0 +1,108 @@
+import waLBerla as wlb
+import math
+
+
+class Scenario:
+    def __init__(self):
+        # physical parameters
+        self.drop_diameter = 20
+        self.pool_depth = self.drop_diameter * 0.5
+
+        self.bond_number = 3.18
+        self.weber_number = 2010
+        self.ohnesorge_number = 0.0384
+
+        self.relaxation_rate_heavy = 1.988
+        self.density_heavy = 1
+        self.density_ratio = 1000
+        self.dynamic_viscosity_ratio = 100
+
+        self.impact_angle_degree = 0    # drop impact angle in degree
+
+        self.interface_thickness = 4
+        self.mobility = 0.03
+
+        self.relaxation_time_heavy = 1 / self.relaxation_rate_heavy
+        self.kinematic_viscosity_heavy = 1 / 3 * (self.relaxation_time_heavy - 0.5)
+        self.dynamic_viscosity_heavy = self.kinematic_viscosity_heavy * self.density_heavy
+
+        self.density_light = self.density_heavy / self.density_ratio
+        self.dynamic_viscosity_light = self.dynamic_viscosity_heavy / self.dynamic_viscosity_ratio
+        self.kinematic_viscosity_light = self.dynamic_viscosity_light / self.density_light
+        self.relaxation_time_light = 3 * self.kinematic_viscosity_light + 0.5
+
+        self.surface_tension = ((self.dynamic_viscosity_heavy / self.ohnesorge_number)**2 / self.drop_diameter
+                                / self.density_heavy)
+
+        self.gravitational_acceleration = (- self.bond_number * self.surface_tension / self.drop_diameter**2 /
+                                           self.density_heavy)
+
+        self.impact_velocity_magnitude = (self.weber_number * self.surface_tension / self.drop_diameter /
+                                          self.density_heavy)**0.5
+
+        self.impact_velocity = (self.impact_velocity_magnitude * math.sin(self.impact_angle_degree * math.pi / 180),
+                                -self.impact_velocity_magnitude * math.cos(self.impact_angle_degree * math.pi / 180),
+                                0)
+
+        self.reference_time = self.drop_diameter / self.impact_velocity_magnitude
+        self.timesteps = 15 * self.reference_time
+        self.vtkWriteFrequency = self.reference_time
+        self.meshWriteFrequency = self.reference_time
+
+        # domain parameters
+        self.size = (self.drop_diameter * 10,
+                     self.drop_diameter * 5,
+                     self.drop_diameter * 10)
+        self.blocks = (1, 1, 1)
+        self.periodic = (1, 0, 1)
+        self.cells = (self.size[0] // self.blocks[0], self.size[1] // self.blocks[1], self.size[2] // self.blocks[2])
+        self.drop_mid_point = (self.size[0] / 2, self.pool_depth + self.drop_diameter / 2, self.size[2] / 2)
+
+        self.scenario = 3   # 1 rising bubble, 2 RTI, 3 drop, 4 taylor bubble set up
+
+        self.counter = 0
+        self.yPositions = []
+
+    @wlb.member_callback
+    def config(self):
+        return {
+            'DomainSetup': {
+                'blocks': self.blocks,
+                'domainSize': self.size,
+                'cellsPerBlock': self.cells,
+                'periodic': self.periodic,
+            },
+            'Parameters': {
+                'timesteps': self.timesteps,
+                'vtkWriteFrequency': self.vtkWriteFrequency,
+                'meshWriteFrequency': self.meshWriteFrequency,
+                'remainingTimeLoggerFrequency': 10.0,
+                'scenario': self.scenario,
+            },
+            'PhysicalParameters': {
+                'density_liquid': self.density_heavy,
+                'density_gas': self.density_light,
+                'surface_tension': self.surface_tension,
+                'mobility': self.mobility,
+                'gravitational_acceleration': self.gravitational_acceleration,
+                'relaxation_time_liquid': self.relaxation_time_heavy - 0.5,
+                'relaxation_time_gas': self.relaxation_time_light - 0.5,
+                'interface_thickness': self.interface_thickness,
+            },
+            'Boundaries': {
+                'Border': [
+                    {'direction': 'N', 'walldistance': -1, 'flag': 'NoSlip'},
+                    {'direction': 'S', 'walldistance': -1, 'flag': 'NoSlip'},
+                ]
+            },
+            'Drop': {
+                'drop_mid_point': self.drop_mid_point,
+                'drop_radius': self.drop_diameter / 2,
+                'pool_depth': self.pool_depth,
+                'impact_velocity': self.impact_velocity,
+            },
+        }
+
+
+scenarios = wlb.ScenarioManager()
+scenarios.add(Scenario())
diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_rising_bubble.py b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_rising_bubble.py
index 5caf4b343..213dd9b1b 100755
--- a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_rising_bubble.py
+++ b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_rising_bubble.py
@@ -50,7 +50,7 @@ class Scenario:
         # everything else
         self.dbFile = "risingBubble3D.db"
 
-        self.scenario = 1  # 1 rising bubble, 2 RTI
+        self.scenario = 1   # 1 rising bubble, 2 RTI, 3 drop, 4 taylor bubble set up
 
         self.counter = 0
         self.yPositions = []
diff --git a/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_taylor_bubble.py b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_taylor_bubble.py
new file mode 100755
index 000000000..f07adfe57
--- /dev/null
+++ b/apps/showcases/PhaseFieldAllenCahn/CPU/multiphase_taylor_bubble.py
@@ -0,0 +1,184 @@
+import os
+import waLBerla as wlb
+import numpy as np
+import pandas as pd
+
+from waLBerla.core_extension import makeSlice
+from waLBerla.tools.sqlitedb import sequenceValuesToScalars
+
+
+class Scenario:
+    def __init__(self):
+        # physical parameters
+        self.tube_diameter = 64
+
+        self.bond_number = 100
+        self.morton_number = 0.015
+
+        self.relaxation_rate_heavy = 1.76
+        self.density_heavy = 1
+        self.density_ratio = 744
+        self.dynamic_viscosity_ratio = 4236
+
+        self.interface_width = 3
+        self.mobility = 0.08
+
+        self.relaxation_time_heavy = 1 / self.relaxation_rate_heavy
+        self.kinematic_viscosity_heavy = 1 / 3 * (self.relaxation_time_heavy - 0.5)
+        self.dynamic_viscosity_heavy = self.density_heavy * self.kinematic_viscosity_heavy
+
+        self.density_light = self.density_heavy / self.density_ratio
+        self.dynamic_viscosity_light = self.dynamic_viscosity_heavy / self.dynamic_viscosity_ratio
+        self.kinematic_viscosity_light = self.dynamic_viscosity_light / self.density_light
+        self.relaxation_time_light = 3 * self.kinematic_viscosity_light + 0.5
+
+        self.surface_tension = (self.bond_number * self.dynamic_viscosity_heavy**4 / self.morton_number /
+                                self.density_heavy**2 / self.tube_diameter**2)**0.5
+
+        self.gravitational_acceleration = - (self.morton_number * self.density_heavy * self.surface_tension**3 /
+                                             self.dynamic_viscosity_heavy**4)
+
+        self.reference_time = (self.tube_diameter / abs(self.gravitational_acceleration))**0.5
+        self.timesteps = 20 * self.reference_time
+
+        self.vtkWriteFrequency = self.reference_time
+        self.dbWriteFrequency = self.reference_time
+        self.meshWriteFrequency = self.reference_time
+
+        # simulation parameters
+        self.size = (self.tube_diameter, self.tube_diameter * 10, self.tube_diameter)
+        self.blocks = (1, 1, 1)
+        self.cells = (self.size[0] // self.blocks[0], self.size[1] // self.blocks[1], self.size[2] // self.blocks[2])
+        self.periodic = (0, 0, 0)
+        self.inner_radius = self.tube_diameter
+
+        self.center_x = self.size[0] / 2
+        self.center_y = self.size[1] / 2
+        self.center_z = self.size[2] / 2
+
+        self.scenario = 4   # 1 rising bubble, 2 RTI, 3 drop, 4 taylor bubble set up
+
+        self.counter = 0
+        self.yPositions = []
+
+        self.eccentricity_or_pipe_ratio = False  # if True eccentricity is conducted otherwise pipe ratio
+        self.ratio = 0
+
+        self.start_transition = (self.size[1] // 2) - 2 * self.tube_diameter
+        self.length_transition = 4 * self.tube_diameter
+
+        setup = "eccentricity" if self.eccentricity_or_pipe_ratio else "ratio"
+
+        self.csv_file = f"Taylor_bubble_D_{self.tube_diameter}_C_{setup}_{self.ratio}_W_" \
+                        f"{self.interface_width}_M_{self.mobility}.csv"
+
+        d = self.tube_diameter / 2
+        dh = self.tube_diameter - d
+
+        resolution = self.tube_diameter / 128
+
+        self.Donut_D = 0.1 * self.tube_diameter / resolution
+        self.Donut_h = dh / 6
+        self.DonutTime = 0.5 * (self.tube_diameter + d) / 2
+
+        self.config_dict = self.config()
+
+    @wlb.member_callback
+    def config(self):
+        return {
+            'DomainSetup': {
+                'blocks': self.blocks,
+                'domainSize': self.size,
+                'cellsPerBlock': self.cells,
+                'diameter': self.tube_diameter,
+                'periodic': self.periodic,
+                'inner_radius': self.inner_radius,
+                'ratio': self.ratio,
+                'start_transition': self.start_transition,
+                'length_transition': self.length_transition,
+                'eccentricity_or_pipe_ration': self.eccentricity_or_pipe_ratio,
+                'tube': True
+            },
+            'Parameters': {
+                'timesteps': self.timesteps,
+                'vtkWriteFrequency': self.vtkWriteFrequency,
+                'dbWriteFrequency': self.dbWriteFrequency,
+                'meshWriteFrequency': self.meshWriteFrequency,
+                'remainingTimeLoggerFrequency': 60.0,
+                'scenario': self.scenario,
+            },
+            'PhysicalParameters': {
+                'density_liquid': self.density_heavy,
+                'density_gas': self.density_light,
+                'surface_tension': self.surface_tension,
+                'mobility': self.mobility,
+                'gravitational_acceleration': self.gravitational_acceleration,
+                'relaxation_time_liquid': self.relaxation_time_heavy - 0.5,
+                'relaxation_time_gas': self.relaxation_time_light - 0.5,
+                'interface_thickness': self.interface_width
+            },
+            'Boundaries': {
+                'Border': [
+                    {'direction': 'N', 'walldistance': -1, 'flag': 'NoSlip'},
+                    {'direction': 'S', 'walldistance': -1, 'flag': 'NoSlip'},
+                    {'direction': 'W', 'walldistance': -1, 'flag': 'NoSlip'},
+                    {'direction': 'E', 'walldistance': -1, 'flag': 'NoSlip'},
+                    {'direction': 'T', 'walldistance': -1, 'flag': 'NoSlip'},
+                    {'direction': 'B', 'walldistance': -1, 'flag': 'NoSlip'},
+                ],
+            },
+            'TaylorBubble': {
+                'bubble_radius': 0.75 * 0.5 * self.tube_diameter,
+                'initial_height': 1 * self.tube_diameter,
+                'length': 3 * self.tube_diameter,
+            }
+        }
+
+    @wlb.member_callback
+    def at_end_of_time_step(self, blocks, **kwargs):
+        t = kwargs["timeStep"]
+        if t % self.dbWriteFrequency == 0:
+            wlb_field = wlb.field.gather(blocks, 'phase', makeSlice[:, :, self.size[2] // 2])
+            if wlb_field:
+                data = {'timestep': t}
+                data.update(self.config_dict['Parameters'])
+                data.update(self.config_dict['DomainSetup'])
+                data.update(self.config_dict['PhysicalParameters'])
+                data.update(self.config_dict['TaylorBubble'])
+                data.update(kwargs)
+
+                phase_field = np.asarray(wlb_field).squeeze()
+                location_of_gas = np.where(phase_field < 0.5)
+                center_of_mass = np.mean(location_of_gas, axis=1)
+
+                assert np.isfinite(np.sum(phase_field)), "NaN detected in the phasefield"
+
+                self.yPositions.append(center_of_mass[1])
+                if len(self.yPositions) > 1:
+                    speed = self.yPositions[-1] - self.yPositions[-2]
+                else:
+                    speed = 0
+
+                data['center_of_mass_x'] = center_of_mass[0]
+                data['center_of_mass_y'] = center_of_mass[1]
+                # data['center_of_mass_z'] = center_of_mass[2]
+
+                data['xCells'] = self.size[0]
+                data['yCells'] = self.size[1]
+                data['zCells'] = self.size[2]
+
+                data['rising_velocity'] = speed
+
+                data['StencilHydroLB'] = kwargs["stencil_hydro"]
+                data['StencilPhaseLB'] = kwargs["stencil_phase"]
+                sequenceValuesToScalars(data)
+
+                df = pd.DataFrame.from_records([data])
+                if not os.path.isfile(self.csv_file):
+                    df.to_csv(self.csv_file, index=False)
+                else:
+                    df.to_csv(self.csv_file, index=False, mode='a', header=False)
+
+
+scenarios = wlb.ScenarioManager()
+scenarios.add(Scenario())
diff --git a/apps/showcases/PhaseFieldAllenCahn/GPU/CMakeLists.txt b/apps/showcases/PhaseFieldAllenCahn/GPU/CMakeLists.txt
index ac4feda33..9116f0b19 100644
--- a/apps/showcases/PhaseFieldAllenCahn/GPU/CMakeLists.txt
+++ b/apps/showcases/PhaseFieldAllenCahn/GPU/CMakeLists.txt
@@ -18,4 +18,4 @@ waLBerla_generate_target_from_python(NAME PhaseFieldCodeGenGPU
 
 waLBerla_add_executable(NAME multiphaseGPU
         FILES multiphase.cpp PythonExports.cpp InitializerFunctions.cpp util.cpp multiphase_codegen.py
-        DEPENDS blockforest core cuda field postprocessing python_coupling lbm geometry timeloop gui PhaseFieldCodeGenGPU)
+        DEPENDS blockforest core cuda field postprocessing python_coupling lbm geometry timeloop PhaseFieldCodeGenGPU)
-- 
GitLab