diff --git a/apps/showcases/CMakeLists.txt b/apps/showcases/CMakeLists.txt
index f601fdd24ebb1454ae518db8405be992f9f0be57..c877683244060ac562f7162c7679285f62395b3a 100644
--- a/apps/showcases/CMakeLists.txt
+++ b/apps/showcases/CMakeLists.txt
@@ -11,6 +11,7 @@ if ( WALBERLA_BUILD_WITH_CODEGEN)
 
    add_subdirectory( Antidunes )
    add_subdirectory( FlowAroundSphere )
+   add_subdirectory( PorousMediaGPU )
 
    if (WALBERLA_BUILD_WITH_PYTHON)
       add_subdirectory( PhaseFieldAllenCahn )
diff --git a/apps/showcases/PorousMediaGPU/CMakeLists.txt b/apps/showcases/PorousMediaGPU/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fdea6fdfa2482c26261aa781b1b0ac6bb79eb7b9
--- /dev/null
+++ b/apps/showcases/PorousMediaGPU/CMakeLists.txt
@@ -0,0 +1,28 @@
+waLBerla_link_files_to_builddir( *.py )
+waLBerla_link_files_to_builddir( *.prm )
+waLBerla_link_files_to_builddir( *.png )
+waLBerla_link_files_to_builddir( *.obj )
+waLBerla_link_files_to_builddir( *.stl )
+waLBerla_link_files_to_builddir( *.mtl )
+
+waLBerla_generate_target_from_python(NAME PorousMediaGPUCodeGen
+        FILE PorousMediaGPU.py
+        OUT_FILES PorousMediaGPUSweepCollection.h PorousMediaGPUSweepCollection.${CODEGEN_FILE_SUFFIX}
+        PorousMediaGPUStorageSpecification.h PorousMediaGPUStorageSpecification.${CODEGEN_FILE_SUFFIX}
+        NoSlip.h NoSlip.${CODEGEN_FILE_SUFFIX}
+        UBB.h UBB.${CODEGEN_FILE_SUFFIX}
+        Outflow.h Outflow.${CODEGEN_FILE_SUFFIX}
+        PorousMediaGPUBoundaryCollection.h
+        PorousMediaGPUInfoHeader.h
+        PorousMediaGPUStaticDefines.h)
+
+if (WALBERLA_BUILD_WITH_CUDA OR WALBERLA_BUILD_WITH_HIP)
+    waLBerla_add_executable ( NAME PorousMediaGPU
+            FILES PorousMediaGPU.cpp Types.h InitializerFunctions.cpp
+            DEPENDS blockforest boundary core field gpu lbm_generated stencil timeloop vtk PorousMediaGPUCodeGen )
+else()
+    waLBerla_add_executable ( NAME PorousMediaGPU
+            FILES PorousMediaGPU.cpp Types.h InitializerFunctions.cpp
+            DEPENDS blockforest boundary core field lbm_generated stencil timeloop vtk PorousMediaGPUCodeGen )
+
+endif(WALBERLA_BUILD_WITH_CUDA OR WALBERLA_BUILD_WITH_HIP)
diff --git a/apps/showcases/PorousMediaGPU/InitializerFunctions.cpp b/apps/showcases/PorousMediaGPU/InitializerFunctions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f3a921e31f04f52062ec3b153b730675a5ee1e49
--- /dev/null
+++ b/apps/showcases/PorousMediaGPU/InitializerFunctions.cpp
@@ -0,0 +1,51 @@
+//======================================================================================================================
+//
+//  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 InitializerFunctions.cpp
+//! \author Markus Holzer <markus.holzer@fau.de>
+//
+//======================================================================================================================
+#include "core/logging/Initialization.h"
+#include "core/math/Constants.h"
+
+#include "field/FlagField.h"
+#include "field/communication/PackInfo.h"
+#include "field/vtk/VTKWriter.h"
+
+
+namespace walberla
+{
+using PhaseField_T = GhostLayerField< real_t, 1 >;
+using FlagField_T  = FlagField< uint8_t >;
+
+
+
+void init_TPMS(const shared_ptr< StructuredBlockStorage >& blocks, const BlockDataID flagFieldID,
+               const FlagUID boundaryFlagUID,
+               const real_t kx, const real_t ky, const real_t kz)
+{
+   const real_t strut = real_c(0.0);
+
+   for (auto& block : *blocks)
+   {
+      const auto flagField    = block.template getData< FlagField_T >(flagFieldID);
+      const auto boundaryFlag = flagField->getOrRegisterFlag(boundaryFlagUID);
+      WALBERLA_FOR_ALL_CELLS_INCLUDING_GHOST_LAYER_XYZ(flagField, Cell globalCell;
+         blocks->transformBlockLocalToGlobalCell(globalCell, block, Cell(x, y, z));
+         const real_t dist = cos(kx * x) * sin(ky * y) + cos(ky * y) * sin(kz * z) + cos(kz * z) * sin(kx * x);
+         if (dist >= strut) addFlag(flagField->get(x, y, z), boundaryFlag);)
+   }
+}
+} // namespace walberla
diff --git a/apps/showcases/PorousMediaGPU/InitializerFunctions.h b/apps/showcases/PorousMediaGPU/InitializerFunctions.h
new file mode 100644
index 0000000000000000000000000000000000000000..2dcdaf3aecbb7d04f16adc33a9895dacbc43f438
--- /dev/null
+++ b/apps/showcases/PorousMediaGPU/InitializerFunctions.h
@@ -0,0 +1,31 @@
+//======================================================================================================================
+//
+//  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 InitializerFunctions.h
+//! \author Markus Holzer <markus.holzer@fau.de>
+//
+//======================================================================================================================
+
+#include "core/logging/Initialization.h"
+#include "field/FlagField.h"
+
+#pragma once
+
+namespace walberla
+{
+void init_TPMS(const shared_ptr< StructuredBlockStorage >& blocks, BlockDataID flagFieldID,
+               FlagUID boundaryFlagUID, real_t kx, real_t ky, real_t kz);
+
+} // namespace walberla
diff --git a/apps/showcases/PorousMediaGPU/PorousMediaGPU.cpp b/apps/showcases/PorousMediaGPU/PorousMediaGPU.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..185a899703c3688e91106d1a7fc487c05289e27a
--- /dev/null
+++ b/apps/showcases/PorousMediaGPU/PorousMediaGPU.cpp
@@ -0,0 +1,259 @@
+//======================================================================================================================
+//
+//  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 PorousMediaGPU.cpp
+//! \author Markus Holzer <markus.holzer@fau.de>
+//
+//======================================================================================================================
+
+#include "blockforest/Initialization.h"
+#include "blockforest/StructuredBlockForest.h"
+#include "blockforest/communication/UniformBufferedScheme.h"
+
+#include "core/Abort.h"
+#include "core/DataTypes.h"
+#include "core/SharedFunctor.h"
+#include "core/debug/CheckFunctions.h"
+#include "core/logging/Logging.h"
+#include "core/math/Vector3.h"
+#include "core/Environment.h"
+#include "core/mpi/MPIManager.h"
+#include "core/MemoryUsage.h"
+#include "core/mpi/Reduce.h"
+#include "core/timing/RemainingTimeLogger.h"
+#include "core/timing/TimingPool.h"
+
+#include "field/AddToStorage.h"
+#include "field/CellCounter.h"
+#include "field/FlagField.h"
+#include "field/StabilityChecker.h"
+#include "field/adaptors/AdaptorCreators.h"
+#include "field/iterators/FieldIterator.h"
+#include "field/vtk/VTKWriter.h"
+#include "field/vtk/FlagFieldCellFilter.h"
+
+#include "geometry/InitBoundaryHandling.h"
+
+#if defined(WALBERLA_BUILD_WITH_GPU_SUPPORT)
+#   include "gpu/AddGPUFieldToStorage.h"
+#   include "gpu/DeviceSelectMPI.h"
+#   include "gpu/ErrorChecking.h"
+#   include "gpu/FieldCopy.h"
+#   include "gpu/HostFieldAllocator.h"
+#   include "gpu/ParallelStreams.h"
+#   include "gpu/communication/UniformGPUScheme.h"
+#endif
+
+#include "lbm_generated/communication/UniformGeneratedPdfPackInfo.h"
+#include "lbm_generated/evaluation/PerformanceEvaluation.h"
+#include "lbm_generated/field/AddToStorage.h"
+#include "lbm_generated/field/PdfField.h"
+
+#if defined(WALBERLA_BUILD_WITH_GPU_SUPPORT)
+#   include "lbm_generated/gpu/AddToStorage.h"
+#   include "lbm_generated/gpu/GPUPdfField.h"
+#   include "lbm_generated/gpu/UniformGeneratedGPUPdfPackInfo.h"
+#endif
+
+#include "timeloop/SweepTimeloop.h"
+#include "vtk/VTKOutput.h"
+
+#include <cstdlib>
+#include <iostream>
+#include <memory>
+
+#include "InitializerFunctions.h"
+#include "Types.h"
+
+#include "PorousMediaGPUStaticDefines.h"
+
+using namespace walberla;
+using macroFieldType = VelocityField_T::value_type;
+
+using BoundaryCollection_T = lbm::PorousMediaGPUBoundaryCollection< FlagField_T >;
+using SweepCollection_T = lbm::PorousMediaGPUSweepCollection;
+
+#if defined(WALBERLA_BUILD_WITH_GPU_SUPPORT)
+using GPUPdfField_T = lbm_generated::GPUPdfField< StorageSpecification_T >;
+using gpu::communication::UniformGPUScheme;
+
+using lbm_generated::UniformGeneratedGPUPdfPackInfo;
+#else
+using PdfField_T = lbm_generated::PdfField< StorageSpecification_T >;
+using blockforest::communication::UniformBufferedScheme;
+
+using lbm_generated::UniformGeneratedPdfPackInfo;
+#endif
+
+int main(int argc, char** argv)
+{
+   Environment env( argc, argv );
+   mpi::MPIManager::instance()->useWorldComm();
+#if defined(WALBERLA_BUILD_WITH_GPU_SUPPORT)
+   gpu::selectDeviceBasedOnMpiRank();
+#endif
+
+   auto config = env.config();
+
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   ///                                        SETUP AND CONFIGURATION                                             ///
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+   auto blocks = blockforest::createUniformBlockGridFromConfig(config);
+   auto parameters          = config->getOneBlock("Parameters");
+   const uint_t timesteps   = parameters.getParameter< uint_t >("timesteps", uint_c(50));
+   const bool gpuEnabledMPI = parameters.getParameter< bool >("gpuEnabledMPI", false);
+
+   const real_t reynoldsNumber    = parameters.getParameter< real_t >("reynoldsNumber");
+   const real_t referenceVelocity = parameters.getParameter< real_t >("referenceVelocity");
+   const real_t inflowVelocity    = parameters.getParameter< real_t >("latticeVelocity");
+
+   const real_t speedOfSound = real_c(real_c(1.0) / std::sqrt( real_c(3.0) ));
+   const real_t machNumber = inflowVelocity / speedOfSound;
+   // TODO define RE
+   const real_t viscosity  = real_c((inflowVelocity * 1.0) / reynoldsNumber);
+   const real_t omega      = real_c(real_c(1.0) / (real_c(3.0) * viscosity + real_c(0.5)));
+
+   IDs ids;
+
+   // Creating fields
+   const StorageSpecification_T StorageSpec = StorageSpecification_T();
+   ids.pdfField  = lbm_generated::addPdfFieldToStorage(blocks, "pdfs", StorageSpec, uint_c(1), field::fzyx);
+
+   auto allocator = make_shared< gpu::HostFieldAllocator<macroFieldType> >(); // use pinned memory allocator for faster CPU-GPU memory transfers
+   ids.velocityField = field::addToStorage< VelocityField_T >(blocks, "vel", macroFieldType(0.0), field::fzyx, uint_c(1), allocator);
+   ids.densityField  = field::addToStorage< ScalarField_T >(blocks, "density", macroFieldType(1.0), field::fzyx, uint_c(1), allocator);
+   ids.flagField     = field::addFlagFieldToStorage< FlagField_T >(blocks, "Boundary Flag Field");
+
+   ids.pdfFieldGPU      = lbm_generated::addGPUPdfFieldToStorage< PdfField_T >(blocks, ids.pdfField, StorageSpec, "pdfs on GPU", true);
+   ids.velocityFieldGPU = gpu::addGPUFieldToStorage< VelocityField_T >(blocks, ids.velocityField, "velocity on GPU", true);
+   ids.densityFieldGPU  = gpu::addGPUFieldToStorage< ScalarField_T >(blocks, ids.densityField, "velocity on GPU", true);
+
+   const Cell innerOuterSplit = Cell(parameters.getParameter< Vector3<cell_idx_t> >("innerOuterSplit", Vector3<cell_idx_t>(1, 1, 1)));
+   SweepCollection_T sweepCollection(blocks, ids.pdfFieldGPU, ids.densityFieldGPU, ids.velocityFieldGPU, omega, innerOuterSplit);
+
+   for (auto& block : *blocks)
+   {
+      sweepCollection.initialise(&block);
+   }
+
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   ///                                      LB SWEEPS AND BOUNDARY HANDLING                                       ///
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+   // Boundaries
+   const FlagUID fluidFlagUID("Fluid");
+   const FlagUID wallFlagUID("NoSlip");
+
+   auto boundariesConfig   = config->getBlock("Boundaries");
+   if (boundariesConfig)
+   {
+      WALBERLA_LOG_INFO_ON_ROOT("Setting boundary conditions")
+      geometry::initBoundaryHandling< FlagField_T >(*blocks, ids.flagField, boundariesConfig);
+      const real_t test = 6;
+      init_TPMS(blocks, ids.flagField, wallFlagUID, test, test, test);
+      geometry::setNonBoundaryCellsToDomain< FlagField_T >(*blocks, ids.flagField, fluidFlagUID);
+   }
+   geometry::setNonBoundaryCellsToDomain< FlagField_T >(*blocks, ids.flagField, fluidFlagUID);
+   BoundaryCollection_T boundaryCollection(blocks, ids.flagField, ids.pdfFieldGPU, fluidFlagUID, inflowVelocity);
+   // BoundaryCollection_T boundaryCollection(blocks, ids.flagField, ids.pdfFieldGPU, fluidFlagUID, inflowVelocity, ids.pdfField);
+
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   ///                                           COMMUNICATION SCHEME                                             ///
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+   UniformGPUScheme< Stencil_T > communication(blocks, gpuEnabledMPI);
+   auto packInfo = std::make_shared<lbm_generated::UniformGeneratedGPUPdfPackInfo< GPUPdfField_T >>(ids.pdfFieldGPU);
+   communication.addPackInfo(packInfo);
+
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   ///                                          TIME STEP DEFINITIONS                                             ///
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   const int streamLowPriority = 0;
+   auto defaultStream = gpu::StreamRAII::newPriorityStream(streamLowPriority);
+
+   SweepTimeloop timeLoop(blocks->getBlockStorage(), timesteps);
+   timeLoop.add() << BeforeFunction(communication.getCommunicateFunctor(), "communication")
+                  << Sweep(boundaryCollection.getSweep(BoundaryCollection_T::ALL, defaultStream), "Boundary Conditions");
+   timeLoop.add() << Sweep(sweepCollection.streamCollide(SweepCollection_T::ALL, defaultStream), "LBM StreamCollide");
+
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+   ///                                             VTK Output                                                     ///
+   //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+   // VTK
+   auto vtkParameters          = config->getOneBlock("VTKWriter");
+   const uint_t vtkWriteFrequency = vtkParameters.getParameter< uint_t >("vtkWriteFrequency", 0);
+   if (vtkWriteFrequency > 0)
+   {
+      auto vtkOutput = vtk::createVTKOutput_BlockData(*blocks, "vtk", vtkWriteFrequency, 0, false, "vtk_out",
+                                                      "simulation_step", false, true, true, false, 0);
+      auto velWriter = make_shared< field::VTKWriter< VelocityField_T, float32 > >(ids.velocityField, "vel");
+      vtkOutput->addCellDataWriter(velWriter);
+
+      vtkOutput->addBeforeFunction([&]() {
+         for (auto& block : *blocks)
+            sweepCollection.calculateMacroscopicParameters(&block);
+         gpu::fieldCpy< VelocityField_T, gpu::GPUField< VelocityField_T::value_type > >(blocks, ids.velocityField, ids.velocityFieldGPU);
+      });
+
+      if (vtkParameters.getParameter< bool >("flag"))
+      {
+         auto flagWriter = make_shared< field::VTKWriter< FlagField_T > >(ids.flagField, "flag");
+         vtkOutput->addCellDataWriter(flagWriter);
+      }
+
+      timeLoop.addFuncAfterTimeStep(vtk::writeFiles(vtkOutput), "VTK Output");
+   }
+
+   WALBERLA_MPI_BARRIER()
+#if defined(WALBERLA_BUILD_WITH_GPU_SUPPORT)
+   WALBERLA_GPU_CHECK(gpuDeviceSynchronize())
+   WALBERLA_GPU_CHECK(gpuPeekAtLastError())
+#endif
+
+   const lbm_generated::PerformanceEvaluation< FlagField_T > performance(blocks, ids.flagField, fluidFlagUID);
+   field::CellCounter< FlagField_T > fluidCells(blocks, ids.flagField, fluidFlagUID);
+   fluidCells();
+
+   WALBERLA_LOG_INFO_ON_ROOT("Starting Simulation")
+   WcTimingPool timeloopTiming;
+   WcTimer simTimer;
+
+   WALBERLA_MPI_BARRIER()
+#if defined(WALBERLA_BUILD_WITH_GPU_SUPPORT)
+   WALBERLA_GPU_CHECK(gpuDeviceSynchronize())
+   WALBERLA_GPU_CHECK(gpuPeekAtLastError())
+#endif
+
+   simTimer.start();
+   timeLoop.run(timeloopTiming);
+#if defined(WALBERLA_BUILD_WITH_GPU_SUPPORT)
+   WALBERLA_GPU_CHECK(gpuDeviceSynchronize())
+   WALBERLA_GPU_CHECK(gpuPeekAtLastError())
+#endif
+   WALBERLA_MPI_BARRIER()
+   simTimer.end();
+   WALBERLA_LOG_INFO_ON_ROOT("Simulation finished")
+   double time = double_c(simTimer.max());
+   WALBERLA_MPI_SECTION() { walberla::mpi::reduceInplace(time, walberla::mpi::MAX); }
+   performance.logResultOnRoot(timesteps, time);
+
+   const auto reducedTimeloopTiming = timeloopTiming.getReduced();
+   WALBERLA_LOG_RESULT_ON_ROOT("Time loop timing:\n" << *reducedTimeloopTiming)
+
+   printResidentMemoryStatistics();
+   return EXIT_SUCCESS;
+}
\ No newline at end of file
diff --git a/apps/showcases/PorousMediaGPU/PorousMediaGPU.prm b/apps/showcases/PorousMediaGPU/PorousMediaGPU.prm
new file mode 100644
index 0000000000000000000000000000000000000000..392caccc003db502c539e90728dfde7271db4164
--- /dev/null
+++ b/apps/showcases/PorousMediaGPU/PorousMediaGPU.prm
@@ -0,0 +1,72 @@
+Parameters
+{
+    reynoldsNumber 2500;
+
+    referenceVelocity 1.0;
+    latticeVelocity 0.05;
+    initialiseWithInletVelocity true;
+
+    timesteps 2;
+
+    processMemoryLimit 512; // MiB
+    innerOuterSplit <1, 1, 1>;
+
+    // GPU related Parameters, only used if GPU is enabled
+    gpuEnabledMPI false;
+}
+
+//! [domainSetup]
+DomainSetup
+{
+    cellsPerBlock < 256, 64, 64 >;
+    blocks    < 1, 1, 1 >;
+    periodic    < false, true, true >;
+}
+//! [domainSetup]
+
+Boundaries
+{
+    Border { direction W;    walldistance -1;  flag UBB; }
+    Border { direction E;    walldistance -1;  flag Outflow; }
+}
+
+
+StabilityChecker
+{
+    checkFrequency 0;
+    streamOutput   false;
+    vtkOutput      true;
+}
+
+VTKWriter
+{
+    vtkWriteFrequency 1;
+    vtkGhostLayers 0;
+    velocity true;
+    density true;
+    flag true;
+    omega false;
+    writeOnlySlice true;
+    sliceThickness 1;
+    writeXZSlice false;
+    amrFileFormat true;
+    oneFilePerProcess false;
+    samplingResolution -1;
+    initialWriteCallsToSkip 0;
+}
+
+Logging
+{
+    logLevel info;  // info progress detail tracing
+    writeSetupForestAndReturn true; // When only one process is used the decomposition is writen and the program terminates
+    readSetupFromFile false;
+    remainingTimeLoggerFrequency 60; // in seconds
+}
+
+Evaluation
+{
+    evaluationCheckFrequency 500;
+    rampUpTime 0;
+    logToStream true;
+    logToFile true;
+}
\ No newline at end of file
diff --git a/apps/showcases/PorousMediaGPU/PorousMediaGPU.py b/apps/showcases/PorousMediaGPU/PorousMediaGPU.py
new file mode 100644
index 0000000000000000000000000000000000000000..f01f9f5386cb8c67ec5493501485a845f8ccb671
--- /dev/null
+++ b/apps/showcases/PorousMediaGPU/PorousMediaGPU.py
@@ -0,0 +1,106 @@
+import sympy as sp
+from pystencils import Target
+
+from pystencils.field import fields
+from pystencils.simp import insert_aliases, insert_constants
+
+from lbmpy import LBStencil, LBMConfig, LBMOptimisation
+from lbmpy.boundaries.boundaryconditions import ExtrapolationOutflow, UBB, NoSlip, FixedDensity
+from lbmpy.creationfunctions import create_lb_collision_rule
+from lbmpy.enums import Method, Stencil
+
+from pystencils_walberla import CodeGeneration, generate_info_header
+from lbmpy_walberla import generate_lbm_package, lbm_boundary_generator
+
+info_header = """
+#pragma once
+#include <map>
+std::map<std::string, std::string> infoMap{{{{"stencil", "{stencil}"}},
+                                           {{"streamingPattern", "{streaming_pattern}"}}, 
+                                           {{"collisionOperator", "{collision_operator}"}}}};
+"""
+
+omega = sp.symbols("omega")
+inlet_velocity = sp.symbols("u_x")
+max_threads = 256
+
+with CodeGeneration() as ctx:
+    target = Target.GPU if ctx.gpu else Target.CPU
+    sweep_params = {'block_size': (128, 1, 1)} if ctx.gpu else {}
+
+    # The application is meant to be compiled with double precision. For single precision, the pdf_dtype can be switched
+    # to float32. In this way the calculations are still performed in double precision while the application can profit
+    # from enhanced performance due to the lower precision of the PDF field
+    dtype = 'float64' if ctx.double_accuracy else 'float32'
+    pdf_dtype = 'float64'
+
+    stencil = LBStencil(Stencil.D3Q19)
+    q = stencil.Q
+    dim = stencil.D
+
+    streaming_pattern = 'pull'
+
+    pdfs = fields(f"pdfs({stencil.Q}): {pdf_dtype}[3D]", layout='fzyx')
+    velocity_field, density_field = fields(f"velocity({dim}), density(1) : {dtype}[{dim}D]", layout='fzyx')
+
+    macroscopic_fields = {'density': density_field, 'velocity': velocity_field}
+
+    # method_enum = Method.CUMULANT
+    method_enum = Method.SRT
+    lbm_config = LBMConfig(
+        method=method_enum,
+        stencil=stencil,
+        relaxation_rate=omega,
+        compressible=True,
+        # subgrid_scale_model=SubgridScaleModel.QR,
+        fourth_order_correction=0.01 if method_enum == Method.CUMULANT and stencil.Q == 27 else False,
+        field_name='pdfs',
+        streaming_pattern=streaming_pattern,
+    )
+
+    lbm_opt = LBMOptimisation(cse_global=False, cse_pdfs=False, field_layout="fzyx",
+                              symbolic_field=pdfs)
+
+    collision_rule = create_lb_collision_rule(lbm_config=lbm_config, lbm_optimisation=lbm_opt)
+    if lbm_config.method == Method.CUMULANT:
+        collision_rule = insert_constants(collision_rule)
+        collision_rule = insert_aliases(collision_rule)
+    lb_method = collision_rule.method
+
+    no_slip = lbm_boundary_generator(class_name='NoSlip', flag_uid='NoSlip',
+                                     boundary_object=NoSlip(), field_data_type=pdf_dtype)
+
+    ubb = lbm_boundary_generator(class_name='UBB', flag_uid='UBB',
+                                 boundary_object=UBB((inlet_velocity, 0.0, 0.0), density=1.0, data_type=dtype),
+                                 field_data_type=pdf_dtype)
+
+    # outflow_boundary = ExtrapolationOutflow(stencil[4], lb_method, data_type=pdf_dtype)
+    # outflow = lbm_boundary_generator(class_name='Outflow', flag_uid='Outflow',
+    #                                  boundary_object=outflow_boundary,
+    #                                  field_data_type=pdf_dtype)
+
+    outflow = lbm_boundary_generator(class_name='Outflow', flag_uid='Outflow',
+                                     boundary_object=FixedDensity(1.0),
+                                     field_data_type=pdf_dtype)
+
+    generate_lbm_package(ctx, name="PorousMediaGPU", collision_rule=collision_rule,
+                         lbm_config=lbm_config, lbm_optimisation=lbm_opt,
+                         nonuniform=False, boundaries=[no_slip, ubb, outflow],
+                         macroscopic_fields=macroscopic_fields, gpu_indexing_params=sweep_params,
+                         target=target, data_type=dtype, pdfs_data_type=pdf_dtype,
+                         max_threads=max_threads)
+
+    field_typedefs = {'VelocityField_T': velocity_field,
+                      'ScalarField_T': density_field}
+
+    # Info header containing correct template definitions for stencil and field
+    generate_info_header(ctx, 'PorousMediaGPUInfoHeader',
+                         field_typedefs=field_typedefs)
+
+    infoHeaderParams = {
+        'stencil': stencil.name.lower(),
+        'streaming_pattern': streaming_pattern,
+        'collision_operator': lbm_config.method.name.lower(),
+    }
+
+    ctx.write_file("PorousMediaGPUStaticDefines.h", info_header.format(**infoHeaderParams))
\ No newline at end of file
diff --git a/apps/showcases/PorousMediaGPU/Types.h b/apps/showcases/PorousMediaGPU/Types.h
new file mode 100644
index 0000000000000000000000000000000000000000..1e8335fdc18dec28a6f3bb7293fc565ca2ea917b
--- /dev/null
+++ b/apps/showcases/PorousMediaGPU/Types.h
@@ -0,0 +1,45 @@
+//======================================================================================================================
+//
+//  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 Types.h
+//! \author Markus Holzer <markus.holzer@fau.de>
+//
+//======================================================================================================================
+# pragma once
+
+#include "domain_decomposition/BlockDataID.h"
+#include "lbm_generated/field/PdfField.h"
+#include "PorousMediaGPUInfoHeader.h"
+
+using namespace walberla;
+
+using StorageSpecification_T = lbm::PorousMediaGPUStorageSpecification;
+using Stencil_T              = StorageSpecification_T::Stencil;
+using CommunicationStencil_T = StorageSpecification_T::CommunicationStencil;
+
+using PdfField_T           = lbm_generated::PdfField< StorageSpecification_T >;
+using FlagField_T          = FlagField< uint8_t >;
+
+struct IDs
+{
+   BlockDataID pdfField;
+   BlockDataID velocityField;
+   BlockDataID densityField;
+   BlockDataID flagField;
+
+   BlockDataID pdfFieldGPU;
+   BlockDataID velocityFieldGPU;
+   BlockDataID densityFieldGPU;
+};
\ No newline at end of file