From 6c99d7f1cbb097076e255a9ea44854409b526bbb Mon Sep 17 00:00:00 2001
From: Thomas Fetzer <thomas.fetzer@iws.uni-stuttgart.de>
Date: Mon, 19 Mar 2018 10:39:49 +0100
Subject: [PATCH] [rans][zeroeq] Implement roughness and various changes

* do NOT use updateStaticWallProperties() in problem constructor
* correct calculation of maximum velocity
* add Laufer velocity profile
* update wall variable names
---
 dumux/freeflow/rans/problem.hh                | 73 ++++++++------
 dumux/freeflow/rans/volumevariables.hh        | 35 +++++--
 dumux/freeflow/rans/vtkoutputfields.hh        |  1 +
 dumux/freeflow/rans/zeroeq/problem.hh         | 95 +++++++++++++++----
 dumux/freeflow/rans/zeroeq/volumevariables.hh | 28 +++++-
 test/freeflow/rans/CMakeLists.txt             |  2 +-
 test/freeflow/rans/laufer_re50000.csv         | 42 ++++++++
 test/freeflow/rans/laufer_re50000_u+y+.csv    |  2 +-
 test/freeflow/rans/pipelauferproblem.hh       |  7 ++
 test/freeflow/rans/test_pipe_laufer.cc        | 29 ++++--
 test/freeflow/rans/test_pipe_laufer.input     |  4 +-
 test/references/pipe_laufer_zeroeq.vtu        | 26 ++++-
 12 files changed, 270 insertions(+), 74 deletions(-)
 create mode 100644 test/freeflow/rans/laufer_re50000.csv

diff --git a/dumux/freeflow/rans/problem.hh b/dumux/freeflow/rans/problem.hh
index 3eb655df03..ef1b32a83e 100644
--- a/dumux/freeflow/rans/problem.hh
+++ b/dumux/freeflow/rans/problem.hh
@@ -86,9 +86,7 @@ public:
     //! The constructor sets the gravity, if desired by the user.
     RANSProblem(std::shared_ptr<const FVGridGeometry> fvGridGeometry)
     : ParentType(fvGridGeometry)
-    {
-        updateStaticWallProperties();
-    }
+    { }
 
     /*!
      * \brief Update the static (solution independent) relations to the walls
@@ -102,17 +100,16 @@ public:
         std::cout << "Update static wall properties. ";
 
         // update size and initial values of the global vectors
-        wallElementIDs_.resize(this->fvGridGeometry().elementMapper().size());
-        wallDistances_.resize(this->fvGridGeometry().elementMapper().size(), std::numeric_limits<Scalar>::max());
-        neighborIDs_.resize(this->fvGridGeometry().elementMapper().size());
-        cellCenters_.resize(this->fvGridGeometry().elementMapper().size(), GlobalPosition(0.0));
+        wallElementID_.resize(this->fvGridGeometry().elementMapper().size());
+        wallDistance_.resize(this->fvGridGeometry().elementMapper().size(), std::numeric_limits<Scalar>::max());
+        neighborID_.resize(this->fvGridGeometry().elementMapper().size());
+        cellCenter_.resize(this->fvGridGeometry().elementMapper().size(), GlobalPosition(0.0));
         velocity_.resize(this->fvGridGeometry().elementMapper().size(), DimVector(0.0));
-        velocityMaximum_.resize(this->fvGridGeometry().elementMapper().size(), DimVector(0.0));
-        velocityMinimum_.resize(this->fvGridGeometry().elementMapper().size(), DimVector(std::numeric_limits<Scalar>::max()));
         velocityGradients_.resize(this->fvGridGeometry().elementMapper().size(), DimMatrix(0.0));
         flowNormalAxis_.resize(this->fvGridGeometry().elementMapper().size(), 0);
         wallNormalAxis_.resize(this->fvGridGeometry().elementMapper().size(), 1);
         kinematicViscosity_.resize(this->fvGridGeometry().elementMapper().size(), 0.0);
+        sandGrainRoughness_.resize(this->fvGridGeometry().elementMapper().size(), 0.0);
 
         // retrieve all wall intersections and corresponding elements
         std::vector<unsigned int> wallElements;
@@ -142,7 +139,7 @@ public:
         for (const auto& element : elements(gridView))
         {
             unsigned int elementID = this->fvGridGeometry().elementMapper().index(element);
-            cellCenters_[elementID] = element.geometry().center();
+            cellCenter_[elementID] = element.geometry().center();
             for (unsigned int i = 0; i < wallPositions.size(); ++i)
             {
                 static const int problemWallNormalAxis
@@ -158,13 +155,14 @@ public:
                 GlobalPosition global = element.geometry().center();
                 global -= wallPositions[i];
                 // second and argument ensures to use only aligned elements
-                if (abs(global[searchAxis]) < wallDistances_[elementID]
+                if (abs(global[searchAxis]) < wallDistance_[elementID]
                     && abs(global[searchAxis]) < global.two_norm() + 1e-8
                     && abs(global[searchAxis]) > global.two_norm() - 1e-8)
                 {
-                    wallDistances_[elementID] = abs(global[searchAxis]);
-                    wallElementIDs_[elementID] = wallElements[i];
+                    wallDistance_[elementID] = abs(global[searchAxis]);
+                    wallElementID_[elementID] = wallElements[i];
                     wallNormalAxis_[elementID] = searchAxis;
+                    sandGrainRoughness_[elementID] = asImp_().sandGrainRoughnessAtPos(wallPositions[i]);
                 }
             }
         }
@@ -175,8 +173,8 @@ public:
             unsigned int elementID = this->fvGridGeometry().elementMapper().index(element);
             for (unsigned int dimIdx = 0; dimIdx < dim; ++dimIdx)
             {
-                neighborIDs_[elementID][dimIdx][0] = elementID;
-                neighborIDs_[elementID][dimIdx][1] = elementID;
+                neighborID_[elementID][dimIdx][0] = elementID;
+                neighborID_[elementID][dimIdx][1] = elementID;
             }
 
             for (const auto& intersection : intersections(gridView, element))
@@ -187,15 +185,15 @@ public:
                 unsigned int neighborID = this->fvGridGeometry().elementMapper().index(intersection.outside());
                 for (unsigned int dimIdx = 0; dimIdx < dim; ++dimIdx)
                 {
-                    if (abs(cellCenters_[elementID][dimIdx] - cellCenters_[neighborID][dimIdx]) > 1e-8)
+                    if (abs(cellCenter_[elementID][dimIdx] - cellCenter_[neighborID][dimIdx]) > 1e-8)
                     {
-                        if (cellCenters_[elementID][dimIdx] > cellCenters_[neighborID][dimIdx])
+                        if (cellCenter_[elementID][dimIdx] > cellCenter_[neighborID][dimIdx])
                         {
-                            neighborIDs_[elementID][dimIdx][0] = neighborID;
+                            neighborID_[elementID][dimIdx][0] = neighborID;
                         }
-                        if (cellCenters_[elementID][dimIdx] < cellCenters_[neighborID][dimIdx])
+                        if (cellCenter_[elementID][dimIdx] < cellCenter_[neighborID][dimIdx])
                         {
-                            neighborIDs_[elementID][dimIdx][1] = neighborID;
+                            neighborID_[elementID][dimIdx][1] = neighborID;
                         }
                     }
                 }
@@ -222,6 +220,10 @@ public:
         static const int flowNormalAxis
             = getParamFromGroup<int>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "RANS.FlowNormalAxis", -1);
 
+        // re-initialize min and max values
+        velocityMaximum_.assign(this->fvGridGeometry().elementMapper().size(), DimVector(0.0));
+        velocityMinimum_.assign(this->fvGridGeometry().elementMapper().size(), DimVector(std::numeric_limits<Scalar>::max()));
+
         // calculate cell-center-averaged velocities
         for (const auto& element : elements(this->fvGridGeometry().gridView()))
         {
@@ -245,7 +247,7 @@ public:
         for (const auto& element : elements(this->fvGridGeometry().gridView()))
         {
             unsigned int elementID = this->fvGridGeometry().elementMapper().index(element);
-            unsigned int wallElementID = wallElementIDs_[elementID];
+            unsigned int wallElementID = wallElementID_[elementID];
 
             Scalar maxVelocity = 0.0;
             for (unsigned int dimIdx = 0; dimIdx < dim; ++dimIdx)
@@ -253,10 +255,10 @@ public:
                 for (unsigned int velIdx = 0; velIdx < dim; ++velIdx)
                 {
                     velocityGradients_[elementID][velIdx][dimIdx]
-                        = (velocity_[neighborIDs_[elementID][dimIdx][1]][velIdx]
-                              - velocity_[neighborIDs_[elementID][dimIdx][0]][velIdx])
-                          / (cellCenters_[neighborIDs_[elementID][dimIdx][1]][dimIdx]
-                              - cellCenters_[neighborIDs_[elementID][dimIdx][0]][dimIdx]);
+                        = (velocity_[neighborID_[elementID][dimIdx][1]][velIdx]
+                              - velocity_[neighborID_[elementID][dimIdx][0]][velIdx])
+                          / (cellCenter_[neighborID_[elementID][dimIdx][1]][dimIdx]
+                              - cellCenter_[neighborID_[elementID][dimIdx][0]][dimIdx]);
                 }
 
                 if (abs(velocity_[elementID][dimIdx]) > abs(velocityMaximum_[wallElementID][dimIdx]))
@@ -302,7 +304,7 @@ public:
     /*!
      * \brief Returns whether a given point is on a wall
      *
-     * \param globalPos The position in global coordinates where the temperature should be specified.
+     * \param globalPos The position in global coordinates.
      */
     bool isOnWall(const GlobalPosition &globalPos) const
     {
@@ -311,11 +313,21 @@ public:
                    "The problem does not provide an isOnWall() method.");
     }
 
+    /*!
+     * \brief Returns the sand-grain roughness \f$\mathrm{[m]}\f$ at a given position
+     *
+     * \param globalPos The position in global coordinates.
+     */
+    Scalar sandGrainRoughnessAtPos(const GlobalPosition &globalPos) const
+    {
+        return 0.0;
+    }
+
 public:
-    std::vector<unsigned int> wallElementIDs_;
-    std::vector<Scalar> wallDistances_;
-    std::vector<std::array<std::array<unsigned int, 2>, dim>> neighborIDs_;
-    std::vector<GlobalPosition> cellCenters_;
+    std::vector<unsigned int> wallElementID_;
+    std::vector<Scalar> wallDistance_;
+    std::vector<std::array<std::array<unsigned int, 2>, dim>> neighborID_;
+    std::vector<GlobalPosition> cellCenter_;
     std::vector<DimVector> velocity_;
     std::vector<DimVector> velocityMaximum_;
     std::vector<DimVector> velocityMinimum_;
@@ -323,6 +335,7 @@ public:
     std::vector<unsigned int> wallNormalAxis_;
     std::vector<unsigned int> flowNormalAxis_;
     std::vector<Scalar> kinematicViscosity_;
+    std::vector<Scalar> sandGrainRoughness_;
 
 private:
     //! Returns the implementation of the problem (i.e. static polymorphism)
diff --git a/dumux/freeflow/rans/volumevariables.hh b/dumux/freeflow/rans/volumevariables.hh
index 828b79a5a6..1bc2bfdf59 100644
--- a/dumux/freeflow/rans/volumevariables.hh
+++ b/dumux/freeflow/rans/volumevariables.hh
@@ -101,25 +101,38 @@ public:
 
         // calculate characteristic properties of the turbulent flow
         elementID_ = problem.fvGridGeometry().elementMapper().index(element);
-        wallElementID_ = problem.wallElementIDs_[elementID_];
-        wallDistance_ = problem.wallDistances_[elementID_];
+        wallElementID_ = problem.wallElementID_[elementID_];
+        wallDistance_ = problem.wallDistance_[elementID_];
         velocity_ = problem.velocity_[elementID_];
+        velocityMaximum_ = problem.velocityMaximum_[wallElementID_];
         velocityGradients_ = problem.velocityGradients_[elementID_];
         unsigned int flowNormalAxis = problem.flowNormalAxis_[elementID_];
         unsigned int wallNormalAxis = problem.wallNormalAxis_[elementID_];
-        Scalar uStar = sqrt(problem.kinematicViscosity_[wallElementID_]
-                            * abs(problem.velocityGradients_[wallElementID_][flowNormalAxis][wallNormalAxis]));
-        yPlus_ = wallDistance_ * uStar / asImp_().kinematicViscosity();
-        yPlus_ = max(yPlus_, 1e-10); // zero values lead to numerical problems in some turbulence models
-        uPlus_ = velocity_[flowNormalAxis] / max(uStar, 1e-10);
+        uStar_ = sqrt(problem.kinematicViscosity_[wallElementID_]
+                      * abs(problem.velocityGradients_[wallElementID_][flowNormalAxis][wallNormalAxis]));
+        uStar_ = max(uStar_, 1e-10); // zero values lead to numerical problems in some turbulence models
+        yPlus_ = wallDistance_ * uStar_ / asImp_().kinematicViscosity();
+        uPlus_ = velocity_[flowNormalAxis] / uStar_;
     };
 
+    /*!
+     * \brief Return the element ID of the control volume.
+     */
+    unsigned int elementID() const
+    { return elementID_; }
+
     /*!
      * \brief Return the velocity vector \f$\mathrm{[m/s]}\f$ at the control volume center.
      */
     DimVector velocity() const
     { return velocity_; }
 
+    /*!
+     * \brief Return the maximum velocity vector \f$\mathrm{[m/s]}\f$ of the wall segment.
+     */
+    DimVector velocityMaximum() const
+    { return velocityMaximum_; }
+
     /*!
      * \brief Return the velocity gradients \f$\mathrm{[1/s]}\f$ at the control volume center.
      */
@@ -132,6 +145,12 @@ public:
     Scalar wallDistance() const
     { return wallDistance_; }
 
+    /*!
+     * \brief Return the wall friction velocity \f$\mathrm{[m/s]}\f$
+     */
+    Scalar uStar() const
+    { return uStar_; }
+
     /*!
      * \brief Return the dimensionless wall distance \f$\mathrm{[-]}\f$.
      */
@@ -183,11 +202,13 @@ private:
 
 protected:
     DimVector velocity_;
+    DimVector velocityMaximum_;
     DimMatrix velocityGradients_;
     Scalar dynamicEddyViscosity_;
     unsigned int elementID_;
     unsigned int wallElementID_;
     Scalar wallDistance_;
+    Scalar uStar_;
     Scalar yPlus_;
     Scalar uPlus_;
 };
diff --git a/dumux/freeflow/rans/vtkoutputfields.hh b/dumux/freeflow/rans/vtkoutputfields.hh
index ff84b83d83..b1a1d3d0a6 100644
--- a/dumux/freeflow/rans/vtkoutputfields.hh
+++ b/dumux/freeflow/rans/vtkoutputfields.hh
@@ -45,6 +45,7 @@ public:
     {
         NavierStokesVtkOutputFields<FVGridGeometry>::init(vtk);
 
+        vtk.addVolumeVariable([](const auto& v){ return v.velocity()[0] / v.velocityMaximum()[0]; }, "v_x/v_x,max");
         vtk.addVolumeVariable([](const auto& v){ return v.velocityGradients()[0]; }, "dv_x/dx_");
         if (dim > 1)
             vtk.addVolumeVariable([](const auto& v){ return v.velocityGradients()[1]; }, "dv_y/dx_");
diff --git a/dumux/freeflow/rans/zeroeq/problem.hh b/dumux/freeflow/rans/zeroeq/problem.hh
index 2c0841d90c..53dccbe458 100644
--- a/dumux/freeflow/rans/zeroeq/problem.hh
+++ b/dumux/freeflow/rans/zeroeq/problem.hh
@@ -82,23 +82,24 @@ public:
     //! The constructor sets the gravity, if desired by the user.
     ZeroEqProblem(std::shared_ptr<const FVGridGeometry> fvGridGeometry)
     : ParentType(fvGridGeometry)
-    {
-        updateStaticWallProperties();
-    }
+    { }
 
     /*!
      * \brief Correct size of the static (solution independent) wall variables
      */
     void updateStaticWallProperties()
     {
+        ParentType::updateStaticWallProperties();
+
         // update size and initial values of the global vectors
         kinematicEddyViscosity_.resize(this->fvGridGeometry().elementMapper().size(), 0.0);
+        additionalRoughnessLength_.resize(this->fvGridGeometry().elementMapper().size(), 0.0);
     }
 
     /*!
      * \brief Update the dynamic (solution dependent) relations to the walls
      *
-     * This calculate all necessary information for the Baldwin-Lomax turbulence model
+     * This calculates the roughness related properties
      *
      * \param curSol The solution vector.
      */
@@ -106,16 +107,67 @@ public:
     {
         ParentType::updateDynamicWallProperties(curSol);
 
+        static const int eddyViscosityModel
+            = getParamFromGroup<int>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "RANS.EddyViscosityModel");
+
+        // calculate additional roughness
+        bool printedRangeWarning = false;
+        for (const auto& element : elements(this->fvGridGeometry().gridView()))
+        {
+            unsigned int elementID = this->fvGridGeometry().elementMapper().index(element);
+
+            auto fvGeometry = localView(this->fvGridGeometry());
+            fvGeometry.bindElement(element);
+            for (auto&& scv : scvs(fvGeometry))
+            {
+                using std::sqrt;
+                using std::exp;
+
+                const int dofIdx = scv.dofIndex();
+                CellCenterPrimaryVariables priVars(curSol[cellCenterIdx][dofIdx]);
+                auto elemSol = elementSolution<SolutionVector, FVGridGeometry>(std::move(priVars));
+                VolumeVariables volVars;
+                volVars.update(elemSol, asImp_(), element, scv);
+
+                Scalar ksPlus = this->sandGrainRoughness_[elementID] * volVars.uStar() / volVars.kinematicViscosity();
+                if (ksPlus > 0 && eddyViscosityModel == Indices::baldwinLomax)
+                {
+                    DUNE_THROW(Dune::NotImplemented, "Roughness is not implemented for the Baldwin-Lomax model.");
+                }
+                if (ksPlus > 2000.)
+                {
+                    std::cout << "info: equivalent sand grain roughness ks+=" << ksPlus << " at " << this->cellCenter_[this->wallElementID_[elementID]]
+                              << " is not in the valid range (ksPlus < 2000),"
+                              << " for high ksPlus values the roughness function reaches a turning point."<< std::endl;
+                    DUNE_THROW(Dune::InvalidStateException, "Unphysical roughness behavior.");
+                }
+                else if (ksPlus > 0.0 && ksPlus < 4.535 && !printedRangeWarning)
+                {
+                    Dune::dinfo << "info: equivalent sand grain roughness ks+=" << ksPlus << " at " << this->cellCenter_[this->wallElementID_[elementID]]
+                                << " is not in the valid range (ksPlus > 4.535) and now set to 0.0"<< std::endl;
+                    ksPlus = 0.0;
+                    printedRangeWarning = true;
+                }
+                additionalRoughnessLength_[elementID] = 0.9 / (volVars.uStar() / volVars.kinematicViscosity())
+                                                        * (sqrt(ksPlus) - ksPlus * exp(-ksPlus / 6.0));
+            }
+        }
+
+        // update routine for specfic models
+        if (eddyViscosityModel == Indices::baldwinLomax)
+            updateBaldwinLomaxProperties();
+    }
+
+    /*!
+     * \brief Update the relations and coefficients for the Baldwin-Lomax turbulence model
+     */
+    void updateBaldwinLomaxProperties()
+    {
         std::vector<Scalar> kinematicEddyViscosityInner(this->fvGridGeometry().elementMapper().size(), 0.0);
         std::vector<Scalar> kinematicEddyViscosityOuter(this->fvGridGeometry().elementMapper().size(), 0.0);
         std::vector<Scalar> kinematicEddyViscosityDifference(this->fvGridGeometry().elementMapper().size(), 0.0);
         std::vector<Scalar> switchingPosition(this->fvGridGeometry().elementMapper().size(), std::numeric_limits<Scalar>::max());
 
-        static const int eddyViscosityModel
-            = getParamFromGroup<int>(GET_PROP_VALUE(TypeTag, ModelParameterGroup), "RANS.EddyViscosityModel");
-        if (eddyViscosityModel != Indices::baldwinLomax)
-            return;
-
         using std::abs;
         using std::exp;
         using std::min;
@@ -139,8 +191,8 @@ public:
         for (const auto& element : elements(this->fvGridGeometry().gridView()))
         {
             unsigned int elementID = this->fvGridGeometry().elementMapper().index(element);
-            unsigned int wallElementID = this->wallElementIDs_[elementID];
-            Scalar wallDistance = this->wallDistances_[elementID];
+            unsigned int wallElementID = this->wallElementID_[elementID];
+            Scalar wallDistance = this->wallDistance_[elementID] + additionalRoughnessLength_[elementID];
             unsigned int flowNormalAxis = this->flowNormalAxis_[elementID];
             unsigned int wallNormalAxis = this->wallNormalAxis_[elementID];
 
@@ -164,8 +216,8 @@ public:
         for (const auto& element : elements(this->fvGridGeometry().gridView()))
         {
             unsigned int elementID = this->fvGridGeometry().elementMapper().index(element);
-            unsigned int wallElementID = this->wallElementIDs_[elementID];
-            Scalar wallDistance = this->wallDistances_[elementID];
+            unsigned int wallElementID = this->wallElementID_[elementID];
+            Scalar wallDistance = this->wallDistance_[elementID] + additionalRoughnessLength_[elementID];
 
             Scalar maxVelocityNorm = 0.0;
             Scalar minVelocityNorm = 0.0;
@@ -191,8 +243,8 @@ public:
         for (const auto& element : elements(this->fvGridGeometry().gridView()))
         {
             unsigned int elementID = this->fvGridGeometry().elementMapper().index(element);
-            unsigned int wallElementID = this->wallElementIDs_[elementID];
-            Scalar wallDistance = this->wallDistances_[elementID];
+            unsigned int wallElementID = this->wallElementID_[elementID];
+            Scalar wallDistance = this->wallDistance_[elementID] + additionalRoughnessLength_[elementID];
 
             // checks if sign switches, by multiplication
             Scalar check = kinematicEddyViscosityDifference[wallElementID] * kinematicEddyViscosityDifference[elementID];
@@ -207,17 +259,20 @@ public:
         for (const auto& element : elements(this->fvGridGeometry().gridView()))
         {
             unsigned int elementID = this->fvGridGeometry().elementMapper().index(element);
-            unsigned int wallElementID = this->wallElementIDs_[elementID];
-            Scalar wallDistance = this->wallDistances_[elementID];
+            unsigned int wallElementID = this->wallElementID_[elementID];
+            Scalar wallDistance = this->wallDistance_[elementID] + additionalRoughnessLength_[elementID];
 
-            kinematicEddyViscosity_[elementID] = (wallDistance >= switchingPosition[wallElementID])
-                                                 ? kinematicEddyViscosityOuter[elementID]
-                                                 : kinematicEddyViscosityInner[elementID];
+            kinematicEddyViscosity_[elementID] = kinematicEddyViscosityInner[elementID];
+            if (wallDistance >= switchingPosition[wallElementID])
+            {
+                kinematicEddyViscosity_[elementID] = kinematicEddyViscosityOuter[elementID];
+            }
         }
     }
 
 public:
     std::vector<Scalar> kinematicEddyViscosity_;
+    std::vector<Scalar> additionalRoughnessLength_;
 
 private:
     //! Returns the implementation of the problem (i.e. static polymorphism)
diff --git a/dumux/freeflow/rans/zeroeq/volumevariables.hh b/dumux/freeflow/rans/zeroeq/volumevariables.hh
index f933af541d..0e80250f2c 100644
--- a/dumux/freeflow/rans/zeroeq/volumevariables.hh
+++ b/dumux/freeflow/rans/zeroeq/volumevariables.hh
@@ -83,10 +83,12 @@ public:
                 const SubControlVolume& scv)
     {
         ParentType::update(elemSol, problem, element, scv);
+        additionalRoughnessLength_ = problem.additionalRoughnessLength_[this->elementID()];
+        yPlusRough_ = wallDistanceRough() * this->uStar() / this->kinematicViscosity();
+
         calculateEddyViscosity(elemSol, problem, element, scv);
     }
 
-
     /*!
      * \brief Calculate and set the dynamic eddy viscosity.
      *
@@ -121,14 +123,14 @@ public:
         }
         else if (eddyViscosityModel == Indices::prandtl)
         {
-            Scalar mixingLength = karmanConstant * asImp_().wallDistance();
+            Scalar mixingLength = karmanConstant * asImp_().wallDistanceRough();
             kinematicEddyViscosity = mixingLength * mixingLength * velGrad;
         }
         else if (eddyViscosityModel == Indices::modifiedVanDriest)
         {
-            Scalar mixingLength = karmanConstant * asImp_().wallDistance()
-                                  * (1.0 - exp(-asImp_().yPlus() / 26.0))
-                                  / sqrt(1.0 - exp(-0.26 * asImp_().yPlus()));
+            Scalar mixingLength = karmanConstant * asImp_().wallDistanceRough()
+                                  * (1.0 - exp(-asImp_().yPlusRough() / 26.0))
+                                  / sqrt(1.0 - exp(-0.26 * asImp_().yPlusRough()));
             kinematicEddyViscosity = mixingLength * mixingLength * velGrad;
         }
         else if (eddyViscosityModel == Indices::baldwinLomax)
@@ -143,6 +145,18 @@ public:
         asImp_().setDynamicEddyViscosity(kinematicEddyViscosity * asImp_().density());
     }
 
+    /*!
+     * \brief Return the wall distance \f$\mathrm{[m]}\f$ including an additional roughness length
+     */
+    Scalar wallDistanceRough() const
+    { return this->wallDistance() + additionalRoughnessLength_; }
+
+    /*!
+     * \brief Return the dimensionless wall distance \f$\mathrm{[-]}\f$  including an additional roughness length
+     */
+    Scalar yPlusRough() const
+    { return yPlusRough_; }
+
 private:
     //! Returns the implementation of the problem (i.e. static polymorphism)
     Implementation &asImp_()
@@ -151,6 +165,10 @@ private:
     //! \copydoc asImp_()
     const Implementation &asImp_() const
     { return *static_cast<const Implementation *>(this); }
+
+protected:
+    Scalar additionalRoughnessLength_;
+    Scalar yPlusRough_;
 };
 
 /*!
diff --git a/test/freeflow/rans/CMakeLists.txt b/test/freeflow/rans/CMakeLists.txt
index 0b3b205ce7..a9e36ffdb4 100644
--- a/test/freeflow/rans/CMakeLists.txt
+++ b/test/freeflow/rans/CMakeLists.txt
@@ -1,5 +1,5 @@
 add_input_file_links()
-dune_symlink_to_source_files(FILES laufer_re50000_u+y+.csv)
+dune_symlink_to_source_files(FILES laufer_re50000.csv laufer_re50000_u+y+.csv)
 
 dune_add_test(NAME test_pipe_laufer
               SOURCES test_pipe_laufer.cc
diff --git a/test/freeflow/rans/laufer_re50000.csv b/test/freeflow/rans/laufer_re50000.csv
new file mode 100644
index 0000000000..d695d674e3
--- /dev/null
+++ b/test/freeflow/rans/laufer_re50000.csv
@@ -0,0 +1,42 @@
+###########
+# Velocity profile for Re=500000
+###########
+# Data taken from:
+# Laufer, J.
+# The structure of turbulence in fully developed pipe flow
+# NACA Report, 1954, 1174, 417-434
+###########
+#y/d,u/u_max
+0.000,0.000
+0.006,0.333
+0.008,0.480
+0.014,0.549
+0.019,0.617
+0.033,0.648
+0.048,0.699
+0.078,0.740
+0.107,0.792
+0.141,0.831
+0.195,0.866
+0.244,0.902
+0.295,0.932
+0.348,0.959
+0.401,0.975
+0.452,0.988
+0.500,1.000
+0.549,0.988
+0.599,0.975
+0.652,0.959
+0.706,0.932
+0.757,0.902
+0.806,0.866
+0.860,0.831
+0.893,0.792
+0.923,0.740
+0.953,0.699
+0.968,0.648
+0.981,0.617
+0.987,0.549
+0.992,0.480
+0.995,0.333
+1.000,0.000
diff --git a/test/freeflow/rans/laufer_re50000_u+y+.csv b/test/freeflow/rans/laufer_re50000_u+y+.csv
index 547f79ab0c..53788c4661 100644
--- a/test/freeflow/rans/laufer_re50000_u+y+.csv
+++ b/test/freeflow/rans/laufer_re50000_u+y+.csv
@@ -1,5 +1,5 @@
 ###########
-# Dimensionless velocity profile for Re=50,000
+# Dimensionless velocity profile for Re=50000
 ###########
 # Original source:
 # Laufer, J.
diff --git a/test/freeflow/rans/pipelauferproblem.hh b/test/freeflow/rans/pipelauferproblem.hh
index a9b0e0c68b..ea8e99b495 100644
--- a/test/freeflow/rans/pipelauferproblem.hh
+++ b/test/freeflow/rans/pipelauferproblem.hh
@@ -105,6 +105,7 @@ public:
     : ParentType(fvGridGeometry), eps_(1e-6)
     {
         inletVelocity_ = getParam<Scalar>("Problem.InletVelocity");
+        sandGrainRoughness_ = getParam<Scalar>("Problem.SandGrainRoughness", 0.0);
     }
 
    /*!
@@ -119,6 +120,11 @@ public:
               || globalPos[1] > this->fvGridGeometry().bBoxMax()[1] - localEps_;
     }
 
+    Scalar sandGrainRoughnessAtPos(const GlobalPosition &globalPos) const
+    {
+        return sandGrainRoughness_;
+    }
+
     bool shouldWriteRestartFile() const
     {
         return false;
@@ -228,6 +234,7 @@ private:
 
     Scalar eps_;
     Scalar inletVelocity_;
+    Scalar sandGrainRoughness_;
     TimeLoopPtr timeLoop_;
 };
 } //end namespace
diff --git a/test/freeflow/rans/test_pipe_laufer.cc b/test/freeflow/rans/test_pipe_laufer.cc
index 261ff49ad4..a348a46983 100644
--- a/test/freeflow/rans/test_pipe_laufer.cc
+++ b/test/freeflow/rans/test_pipe_laufer.cc
@@ -142,6 +142,7 @@ int main(int argc, char** argv) try
     x[cellCenterIdx].resize(numDofsCellCenter);
     x[faceIdx].resize(numDofsFace);
     problem->applyInitialSolution(x);
+    problem->updateStaticWallProperties();
     problem->updateDynamicWallProperties(x);
     auto xOld = x;
 
@@ -205,8 +206,9 @@ int main(int argc, char** argv) try
     ////////////////////////////////////////////////////////////
 
 #if HAVE_PVPYTHON
-    bool shouldPlot = getParam<bool>("Output.PlotVelocityProfiles", false);
-    if (shouldPlot)
+    bool plotLawOfTheWall = getParam<bool>("Output.PlotLawOfTheWall", false);
+    bool plotVelocityProfile = getParam<bool>("Output.PlotVelocityProfile", false);
+    if (plotLawOfTheWall || plotVelocityProfile)
     {
         char fileName[255];
         std::string fileNameFormat = "%s-%05d";
@@ -219,26 +221,37 @@ int main(int argc, char** argv) try
         // execute the pvpython script
         std::string command = std::string(PVPYTHON_EXECUTABLE) + " " + script
                               + " -f " + vtuFileName
-                              + " -v 1"
+                              + " -v 2"
                               + " -r 10000";
         syscom =  command + " -p1 8.0 0.0 0.0"
                           + " -p2 8.0 0.2469 0.0"
                           + " -of " + std::string(fileName) + "\n";
+//         std::cout << syscom << std::endl;
         system(syscom.c_str());
 
-
         char gnuplotFileName[255];
         Dumux::GnuplotInterface<Scalar> gnuplot;
-        sprintf(gnuplotFileName, fileNameFormat.c_str(), "velProfiles", timeLoop->timeStepIndex());
-        gnuplot.setOpenPlotWindow(shouldPlot);
+        sprintf(gnuplotFileName, fileNameFormat.c_str(), "lawOfTheWall", timeLoop->timeStepIndex());
+        gnuplot.setOpenPlotWindow(plotLawOfTheWall);
         gnuplot.setDatafileSeparator(',');
         gnuplot.resetPlot();
         gnuplot.setXlabel("y^+ [-]");
         gnuplot.setYlabel("u_+ [-]");
         gnuplot.setOption("set log x");
         gnuplot.setOption("set xrange [1:3000]");
-        gnuplot.addFileToPlot("laufer_re50000_u+y+.csv", "u 1:2 w p t 'Laufer1954a, Re=50000'");
-        gnuplot.addFileToPlot(std::string(fileName) + ".csv", "u 10:11 w l");
+        gnuplot.addFileToPlot("laufer_re50000_u+y+.csv", "u 1:2 w p t 'Laufer 1954, Re=50000'");
+        gnuplot.addFileToPlot(std::string(fileName) + ".csv", "u 11:12 w l");
+        gnuplot.plot(std::string(gnuplotFileName));
+
+        sprintf(gnuplotFileName, fileNameFormat.c_str(), "velProfile", timeLoop->timeStepIndex());
+        gnuplot.resetAll();
+        gnuplot.setOpenPlotWindow(plotVelocityProfile);
+        gnuplot.setDatafileSeparator(',');
+        gnuplot.setXlabel("v_x/v_{x,max} [-]");
+        gnuplot.setYRange(0.0, 1.0);
+        gnuplot.setYlabel("y [-]");
+        gnuplot.addFileToPlot("laufer_re50000.csv", "u 2:1 w p t 'Laufer 1954, Re=50000'");
+        gnuplot.addFileToPlot(std::string(fileName) + ".csv", "u 5:($23/0.2456) w l");
         gnuplot.plot(std::string(gnuplotFileName));
     }
 #endif
diff --git a/test/freeflow/rans/test_pipe_laufer.input b/test/freeflow/rans/test_pipe_laufer.input
index f507011c75..9acb019e21 100644
--- a/test/freeflow/rans/test_pipe_laufer.input
+++ b/test/freeflow/rans/test_pipe_laufer.input
@@ -11,12 +11,14 @@ Cells1 = 25 25
 Grading1 = 1.2 -1.2
 
 [Output]
-PlotVelocityProfiles = true
+PlotLawOfTheWall = true
+PlotVelocityProfile = true
 
 [Problem]
 Name = pipe_laufer_zeroeq
 InletVelocity = 2.5 # [m/s]
 EnableGravity = false
+SandGrainRoughness = 0.0 # [m] # not implemented for EddyViscosityModel = 3
 
 [RANS]
 EddyViscosityModel = 3
diff --git a/test/references/pipe_laufer_zeroeq.vtu b/test/references/pipe_laufer_zeroeq.vtu
index 4f3ada09cb..d1a4169806 100644
--- a/test/references/pipe_laufer_zeroeq.vtu
+++ b/test/references/pipe_laufer_zeroeq.vtu
@@ -2,7 +2,7 @@
 <VTKFile type="UnstructuredGrid" version="0.1" byte_order="LittleEndian">
   <UnstructuredGrid>
     <Piece NumberOfCells="256" NumberOfPoints="289">
-      <CellData Scalars="p" Vectors="velocity_Air (m/s)">
+      <CellData Scalars="p" Vectors="dv_x/dx_">
         <DataArray type="Float32" Name="p" NumberOfComponents="1" format="ascii">
           100001 100001 100001 100001 100001 100001 100001 100001 100001 100000 100000 100000
           100000 100000 100000 100000 100001 100001 100001 100001 100001 100001 100001 100001
@@ -27,6 +27,30 @@
           100001 100001 100001 100001 100001 100001 100001 100001 100001 100000 100000 100000
           100000 100000 100000 100000
         </DataArray>
+        <DataArray type="Float32" Name="v_x/v_x,max" NumberOfComponents="1" format="ascii">
+          0.824026 0.577694 0.494709 0.478182 0.462302 0.44743 0.440993 0.437014 0.431486 0.425143 0.42038 0.419126
+          0.419458 0.419144 0.418159 0.417228 0.946449 0.837676 0.750475 0.702608 0.67421 0.658205 0.654306 0.651134
+          0.643928 0.635299 0.628737 0.626977 0.626963 0.62594 0.62425 0.622795 0.984664 0.939124 0.877889 0.824945
+          0.787385 0.762412 0.747611 0.736606 0.725543 0.714694 0.708117 0.708114 0.709018 0.7078 0.705826 0.704164
+          0.997136 0.984401 0.956349 0.918926 0.883439 0.853441 0.829141 0.810303 0.794956 0.781691 0.772737 0.769462
+          0.767816 0.765339 0.762618 0.760546 0.999713 0.997748 0.990327 0.975018 0.955061 0.933334 0.911037 0.890703
+          0.873326 0.858481 0.846094 0.836777 0.83034 0.825476 0.821414 0.818646 1 0.999888 0.998978 0.995913
+          0.990388 0.98199 0.970179 0.956788 0.943684 0.931463 0.920136 0.909932 0.901567 0.894987 0.88971 0.886277
+          0.999988 1 1 0.999791 0.9993 0.998099 0.995474 0.991556 0.98692 0.981885 0.97636 0.97039
+          0.964742 0.959863 0.955718 0.952956 0.999959 0.999947 0.999996 1 1 1 1 1
+          1 1 1 1 1 1 1 1 0.999959 0.999947 0.999996 1
+          1 1 1 1 1 1 1 1 1 1 1 1
+          0.999988 1 1 0.999791 0.9993 0.998099 0.995474 0.991556 0.98692 0.981885 0.97636 0.97039
+          0.964742 0.959863 0.955718 0.952956 1 0.999888 0.998978 0.995913 0.990388 0.98199 0.970179 0.956788
+          0.943684 0.931463 0.920136 0.909932 0.901567 0.894987 0.88971 0.886277 0.999713 0.997748 0.990327 0.975018
+          0.955061 0.933334 0.911037 0.890703 0.873326 0.858481 0.846094 0.836777 0.83034 0.825476 0.821414 0.818646
+          0.997136 0.984401 0.956349 0.918926 0.883439 0.853441 0.829141 0.810303 0.794956 0.781691 0.772737 0.769462
+          0.767816 0.765339 0.762618 0.760546 0.984664 0.939124 0.877889 0.824945 0.787385 0.762412 0.747611 0.736606
+          0.725543 0.714694 0.708117 0.708114 0.709018 0.7078 0.705826 0.704164 0.946449 0.837676 0.750475 0.702608
+          0.67421 0.658205 0.654306 0.651134 0.643928 0.635299 0.628737 0.626977 0.626963 0.62594 0.62425 0.622795
+          0.824026 0.577694 0.494709 0.478182 0.462302 0.44743 0.440993 0.437014 0.431486 0.425143 0.42038 0.419126
+          0.419458 0.419144 0.418159 0.417228
+        </DataArray>
         <DataArray type="Float32" Name="p_rel" NumberOfComponents="1" format="ascii">
           1.30543 1.17114 1.0534 0.957444 0.867397 0.780484 0.700841 0.624126 0.545742 0.467086 0.389893 0.318209
           0.249301 0.178674 0.107209 0.0356539 1.30533 1.17127 1.05352 0.957484 0.867414 0.780489 0.700836 0.624126
-- 
GitLab