From 7510929d02e2961b5bc3b4839e7bff16c1d72641 Mon Sep 17 00:00:00 2001
From: Kilian Weishaupt <kilian.weishaupt@iws.uni-stuttgart.de>
Date: Mon, 8 Oct 2018 18:13:38 +0200
Subject: [PATCH] [porousmediumflow] Port Forchheimer to dumux3.0-git

* only works for cctpfa with diagonal permeability tensors so far
---
 .../cellcentered/tpfa/forchheimerslaw.hh      | 558 ++++++++++++++++++
 dumux/discretization/forchheimerslaw.hh       |  55 ++
 2 files changed, 613 insertions(+)
 create mode 100644 dumux/discretization/cellcentered/tpfa/forchheimerslaw.hh
 create mode 100644 dumux/discretization/forchheimerslaw.hh

diff --git a/dumux/discretization/cellcentered/tpfa/forchheimerslaw.hh b/dumux/discretization/cellcentered/tpfa/forchheimerslaw.hh
new file mode 100644
index 0000000000..8ec28c739e
--- /dev/null
+++ b/dumux/discretization/cellcentered/tpfa/forchheimerslaw.hh
@@ -0,0 +1,558 @@
+// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+// vi: set et ts=4 sw=4 sts=4:
+/*****************************************************************************
+ *   See the file COPYING for full copying permissions.                      *
+ *                                                                           *
+ *   This program 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 2 of the License, or       *
+ *   (at your option) any later version.                                     *
+ *                                                                           *
+ *   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ *****************************************************************************/
+/*!
+ * \file
+ * \ingroup CCTpfaDiscretization
+ * \brief Forchheimers's law for cell-centered finite volume schemes with two-point flux approximation
+ */
+#ifndef DUMUX_DISCRETIZATION_CC_TPFA_FORCHHEIMERS_LAW_HH
+#define DUMUX_DISCRETIZATION_CC_TPFA_FORCHHEIMERS_LAW_HH
+
+#include <dune/common/fvector.hh>
+#include <dune/common/fmatrix.hh>
+
+#include <dumux/common/math.hh>
+#include <dumux/common/parameters.hh>
+#include <dumux/common/properties.hh>
+#include <dumux/common/typetraits/typetraits.hh>
+
+#include <dumux/discretization/methods.hh>
+#include <dumux/discretization/cellcentered/tpfa/darcyslaw.hh>
+
+namespace Dumux {
+
+// forward declarations
+template<class TypeTag, DiscretizationMethod discMethod>
+class ForchheimersLawImplementation;
+
+/*!
+ * \ingroup CCTpfaDiscretization
+ * \brief Forchheimer's law for cell-centered finite volume schemes with two-point flux approximation
+ * \note Forchheimer's law is specialized for network and surface grids (i.e. if grid dim < dimWorld)
+ * \tparam Scalar the scalar type for scalar physical quantities
+ * \tparam FVGridGeometry the grid geometry
+ * \tparam isNetwork whether we are computing on a network grid embedded in a higher world dimension
+ */
+template<class Scalar, class FVGridGeometry, bool isNetwork>
+class CCTpfaForchheimersLaw;
+
+/*!
+ * \ingroup CCTpfaDiscretization
+ * \brief Forchheimer's law for cell-centered finite volume schemes with two-point flux approximation
+ * \note Forchheimer's law is specialized for network and surface grids (i.e. if grid dim < dimWorld)
+ */
+template <class TypeTag>
+class ForchheimersLawImplementation<TypeTag, DiscretizationMethod::cctpfa>
+: public CCTpfaForchheimersLaw<typename GET_PROP_TYPE(TypeTag, Scalar),
+                         typename GET_PROP_TYPE(TypeTag, FVGridGeometry),
+                         (GET_PROP_TYPE(TypeTag, FVGridGeometry)::GridView::dimension < GET_PROP_TYPE(TypeTag, FVGridGeometry)::GridView::dimensionworld)>
+{};
+
+/*!
+ * \ingroup CCTpfaDiscretization
+ * \brief Class that fills the cache corresponding to tpfa Forchheimer's Law
+ */
+template<class FVGridGeometry>
+class TpfaForchheimersLawCacheFiller
+{
+    using FVElementGeometry = typename FVGridGeometry::LocalView;
+    using SubControlVolumeFace = typename FVGridGeometry::SubControlVolumeFace;
+    using Element = typename FVGridGeometry::GridView::template Codim<0>::Entity;
+
+public:
+    //! Function to fill a TpfaForchheimersLawCache of a given scvf
+    //! This interface has to be met by any advection-related cache filler class
+    //! TODO: Probably get cache type out of the filler
+    template<class FluxVariablesCache, class Problem, class ElementVolumeVariables, class FluxVariablesCacheFiller>
+    static void fill(FluxVariablesCache& scvfFluxVarsCache,
+                     const Problem& problem,
+                     const Element& element,
+                     const FVElementGeometry& fvGeometry,
+                     const ElementVolumeVariables& elemVolVars,
+                     const SubControlVolumeFace& scvf,
+                     const FluxVariablesCacheFiller& fluxVarsCacheFiller)
+    {
+        scvfFluxVarsCache.updateAdvection(problem, element, fvGeometry, elemVolVars, scvf);
+    }
+};
+
+/*!
+ * \ingroup CCTpfaDiscretization
+ * \brief The cache corresponding to tpfa Forchheimer's Law
+ */
+template<class AdvectionType, class FVGridGeometry>
+class TpfaForchheimersLawCache
+{
+    using Scalar = typename AdvectionType::Scalar;
+    using FVElementGeometry = typename FVGridGeometry::LocalView;
+    using SubControlVolumeFace = typename FVGridGeometry::SubControlVolumeFace;
+    using Element = typename FVGridGeometry::GridView::template Codim<0>::Entity;
+    static constexpr int dimWorld = FVGridGeometry::GridView::dimensionworld;
+    using DimWorldMatrix = Dune::FieldMatrix<Scalar, dimWorld, dimWorld>;
+
+public:
+    using Filler = TpfaForchheimersLawCacheFiller<FVGridGeometry>;
+
+    template<class Problem, class ElementVolumeVariables>
+    void updateAdvection(const Problem& problem,
+                         const Element& element,
+                         const FVElementGeometry& fvGeometry,
+                         const ElementVolumeVariables& elemVolVars,
+                         const SubControlVolumeFace &scvf)
+    {
+        tij_ = AdvectionType::calculateTransmissibility(problem, element, fvGeometry, elemVolVars, scvf);
+        harmonicMeanSqrtK_ = AdvectionType::calculateHarmonicMeanSqrtPermeability(problem, elemVolVars, scvf);
+    }
+
+    const Scalar& advectionTij() const
+    { return tij_; }
+
+    const DimWorldMatrix& harmonicMeanSqrtPermeability() const
+    { return harmonicMeanSqrtK_; }
+
+private:
+    Scalar tij_;
+    DimWorldMatrix harmonicMeanSqrtK_;
+};
+
+/*!
+ * \ingroup CCTpfaDiscretization
+ * \brief Specialization of the CCTpfaForchheimersLaw grids where dim=dimWorld
+ */
+template<class ScalarType, class FVGridGeometry>
+class CCTpfaForchheimersLaw<ScalarType, FVGridGeometry, /*isNetwork*/ false>
+{
+    using ThisType = CCTpfaForchheimersLaw<ScalarType, FVGridGeometry, /*isNetwork*/ false>;
+    using FVElementGeometry = typename FVGridGeometry::LocalView;
+    using SubControlVolume = typename FVGridGeometry::SubControlVolume;
+    using SubControlVolumeFace = typename FVGridGeometry::SubControlVolumeFace;
+    using GridView = typename FVGridGeometry::GridView;
+    using Element = typename GridView::template Codim<0>::Entity;
+
+    static constexpr int dim = GridView::dimension;
+    static constexpr int dimWorld = GridView::dimensionworld;
+
+    using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
+    using DimWorldVector = Dune::FieldVector<ScalarType, dimWorld>;
+    using DimWorldMatrix = Dune::FieldMatrix<ScalarType, dimWorld, dimWorld>;
+
+    using DarcysLaw = CCTpfaDarcysLaw<ScalarType, FVGridGeometry, /*isNetwork*/ false>;
+
+  public:
+    //! state the scalar type of the law
+    using Scalar = ScalarType;
+
+    //! state the discretization method this implementation belongs to
+    static const DiscretizationMethod discMethod = DiscretizationMethod::cctpfa;
+
+    //! state the type for the corresponding cache
+    using Cache = TpfaForchheimersLawCache<ThisType, FVGridGeometry>;
+
+    /*! \brief Compute the advective flux using the Forchheimer equation
+    *
+    * see e.g. Nield & Bejan: Convection in Porous Media \cite nield2006
+    *
+    * The relative passability \f$ \eta_r\f$ is the "Forchheimer-equivalent" to the relative
+    * permeability \f$ k_r\f$.
+    * We use the same function as for \f$ k_r \f$ (VG, BC, linear) other authors use a simple
+    * power law e.g.: \f$\eta_{rw} = S_w^3\f$
+    *
+    * Some rearrangements have been made to the original Forchheimer relation:
+    *
+    * \f[ \mathbf{v_\alpha} + c_F \sqrt{\mathbf{K}} \frac{\rho_\alpha}{\mu_\alpha }
+    *     |\mathbf{v_\alpha}| \mathbf{v_\alpha}
+    *     + \frac{k_{r \alpha}}{\mu_\alpha} \mathbf{K} \nabla \left(p_\alpha
+    *     + \rho_\alpha g z \right)=  0
+    * \f]
+    *
+    * This already includes the assumption \f$ k_r(S_w) = \eta_r(S_w)\f$:
+    * - \f$\eta_{rw} = S_w^x\f$ looks very similar to e.g. Van Genuchten relative permeabilities
+    * - Fichot, et al. (2006), Nuclear Engineering and Design, state that several authors claim
+    *   that \f$ k_r(S_w), \eta_r(S_w)\f$ can be chosen equal
+    * - It leads to the equation not degenerating for the case of \f$S_w=1\f$, because I do not
+    *   need to multiply with two different functions, and therefore there are terms not being
+    *   zero.
+    * - If this assumption is not to be made: Some regularization needs to be introduced ensuring
+    *   that not all terms become zero for \f$S_w=1\f$.
+    *
+    * This non-linear equations is solved for \f$\mathbf{v_\alpha}\f$ using Newton's method
+    * and an analytical derivative w.r.t. \f$\mathbf{v_\alpha}\f$.
+    *
+    * The gradient of the Forchheimer relations looks as follows (mind that \f$\sqrt{\mathbf{K}}\f$
+    * is a tensor):
+    *
+    * \f[  f\left(\mathbf{v_\alpha}\right) =
+    * \left(
+    * \begin{array}{ccc}
+    * 1 & 0 &0 \\
+    * 0 & 1 &0 \\
+    * 0 & 0 &1 \\
+    * \end{array}
+    * \right)
+    * +
+    * c_F \frac{\rho_\alpha}{\mu_\alpha} |\mathbf{v}_\alpha| \sqrt{\mathbf{K}}
+    * +
+    * c_F \frac{\rho_\alpha}{\mu_\alpha}\frac{1}{|\mathbf{v}_\alpha|} \sqrt{\mathbf{K}}
+    * \left(
+    * \begin{array}{ccc}
+    * v_x^2 & v_xv_y & v_xv_z \\
+    * v_yv_x & v_{y}^2 & v_yv_z \\
+    * v_zv_x & v_zv_y &v_{z}^2 \\
+    * \end{array}
+    * \right)
+    *  \f]
+    *
+    * \note We restrict the use of Forchheimer's law to diagonal permeability tensors so far. This might be changed to
+    * general tensors using eigenvalue decomposition to get \f$\sqrt{\mathbf{K}}\f$
+    */
+    template<class Problem, class ElementVolumeVariables, class ElementFluxVarsCache>
+    static Scalar flux(const Problem& problem,
+                       const Element& element,
+                       const FVElementGeometry& fvGeometry,
+                       const ElementVolumeVariables& elemVolVars,
+                       const SubControlVolumeFace& scvf,
+                       int phaseIdx,
+                       const ElementFluxVarsCache& elemFluxVarsCache)
+    {
+        const auto velocity = velocity_(problem, element, fvGeometry, elemVolVars, scvf, phaseIdx, elemFluxVarsCache);
+        Scalar flux = velocity * scvf.unitOuterNormal();
+        flux *= scvf.area();
+        return flux;
+    }
+
+    //! The flux variables cache has to be bound to an element prior to flux calculations
+    //! During the binding, the transmissibility will be computed and stored using the method below.
+    template<class Problem, class ElementVolumeVariables>
+    static Scalar calculateTransmissibility(const Problem& problem,
+                                            const Element& element,
+                                            const FVElementGeometry& fvGeometry,
+                                            const ElementVolumeVariables& elemVolVars,
+                                            const SubControlVolumeFace& scvf)
+    {
+        return DarcysLaw::calculateTransmissibility(problem, element, fvGeometry, elemVolVars, scvf);
+    }
+
+    /*! \brief Returns the harmonic mean of \f$\sqrt{K_0}\f$ and \f$\sqrt{K_1}\f$.
+     *
+     * This is a specialization for scalar-valued permeabilities which returns a tensor with identical diagonal entries.
+     */
+    template<class Problem, class ElementVolumeVariables,
+             bool scalarPerm = std::is_same<typename Problem::SpatialParams::PermeabilityType, Scalar>::value,
+             std::enable_if_t<scalarPerm, int> = 0>
+    static DimWorldMatrix calculateHarmonicMeanSqrtPermeability(const Problem& problem,
+                                                                const ElementVolumeVariables& elemVolVars,
+                                                                const SubControlVolumeFace& scvf)
+    {
+        using std::sqrt;
+        Scalar harmonicMeanSqrtK(0.0);
+
+        const auto insideScvIdx = scvf.insideScvIdx();
+        const auto& insideVolVars = elemVolVars[insideScvIdx];
+        const Scalar Ki = getPermeability_(problem, insideVolVars, scvf.ipGlobal());
+        const Scalar sqrtKi = sqrt(Ki);
+
+        if (!scvf.boundary())
+        {
+            const auto outsideScvIdx = scvf.outsideScvIdx();
+            const auto& outsideVolVars = elemVolVars[outsideScvIdx];
+            const Scalar Kj = getPermeability_(problem, outsideVolVars, scvf.ipGlobal());
+            const Scalar sqrtKj = sqrt(Kj);
+            harmonicMeanSqrtK = problem.spatialParams().harmonicMean(sqrtKi, sqrtKj, scvf.unitOuterNormal());
+        }
+        else
+            harmonicMeanSqrtK = sqrtKi;
+
+        // Convert to tensor
+        DimWorldMatrix result(0.0);
+        for (int i = 0; i < dimWorld; ++i)
+            result[i][i] = harmonicMeanSqrtK;
+
+        return result;
+    }
+
+    /*! \brief Returns the harmonic mean of \f$\sqrt{\mathbf{K_0}}\f$ and \f$\sqrt{\mathbf{K_1}}\f$.
+     *
+     * This is a specialization for tensor-valued permeabilities.
+     */
+    template<class Problem, class ElementVolumeVariables,
+             bool scalarPerm = std::is_same<typename Problem::SpatialParams::PermeabilityType, Scalar>::value,
+             std::enable_if_t<!scalarPerm, int> = 0>
+    static DimWorldMatrix calculateHarmonicMeanSqrtPermeability(const Problem& problem,
+                                                                const ElementVolumeVariables& elemVolVars,
+                                                                const SubControlVolumeFace& scvf)
+    {
+        using std::sqrt;
+        DimWorldMatrix sqrtKi(0.0);
+        DimWorldMatrix sqrtKj(0.0);
+        DimWorldMatrix harmonicMeanSqrtK(0.0);
+
+        const auto insideScvIdx = scvf.insideScvIdx();
+        const auto& insideVolVars = elemVolVars[insideScvIdx];
+        const auto& Ki = getPermeability_(problem, insideVolVars, scvf.ipGlobal());
+        // Make sure the permeability matrix does not have off-diagonal entries
+        // TODO implement method using eigenvalues and eigenvectors for general tensors
+        if (!isDiagonal_(Ki))
+            DUNE_THROW(Dune::NotImplemented, "Only diagonal permeability tensors are supported.");
+
+        for (int i = 0; i < dim; ++i)
+            sqrtKi[i][i] = sqrt(Ki[i][i]);
+
+        if (!scvf.boundary())
+        {
+            const auto outsideScvIdx = scvf.outsideScvIdx();
+            const auto& outsideVolVars = elemVolVars[outsideScvIdx];
+            const auto& Kj = getPermeability_(problem, outsideVolVars, scvf.ipGlobal());
+            // Make sure the permeability matrix does not have off-diagonal entries
+            if (!isDiagonal_(Kj))
+                DUNE_THROW(Dune::NotImplemented, "Only diagonal permeability tensors are supported.");
+
+            for (int i = 0; i < dim; ++i)
+                sqrtKj[i][i] = sqrt(Kj[i][i]);
+
+            harmonicMeanSqrtK = problem.spatialParams().harmonicMean(sqrtKi, sqrtKj, scvf.unitOuterNormal());
+        }
+        else
+            harmonicMeanSqrtK = sqrtKi;
+
+        return harmonicMeanSqrtK;
+    }
+
+private:
+
+    //! Computes the face velocity based on the Forchheimer equation
+    template<class Problem, class ElementVolumeVariables, class ElementFluxVarsCache>
+    static DimWorldVector velocity_(const Problem& problem,
+                                    const Element& element,
+                                    const FVElementGeometry& fvGeometry,
+                                    const ElementVolumeVariables& elemVolVars,
+                                    const SubControlVolumeFace& scvf,
+                                    int phaseIdx,
+                                    const ElementFluxVarsCache& elemFluxVarsCache)
+    {
+        // Get the volume flux based on Darcy's law. The value returned by this method needs to be multiplied with the
+        // mobility (upwinding).
+        Scalar darcyFlux = DarcysLaw::flux(problem, element, fvGeometry, elemVolVars, scvf, phaseIdx, elemFluxVarsCache);
+        auto upwindTerm = [phaseIdx](const auto& volVars){ return volVars.mobility(phaseIdx); };
+        const Scalar darcyUpwindMobility = doUpwind_(scvf, elemVolVars, upwindTerm, !std::signbit(darcyFlux) /*insideIsUpstream*/);
+        darcyFlux *= darcyUpwindMobility;
+
+        // Convert to Darcy velocity
+        DimWorldVector darcyVelocity = scvf.unitOuterNormal();
+        darcyVelocity *= darcyFlux / scvf.area();
+
+        // Get the harmonic mean of the square root of permeability
+        const auto& fluxVarsCache = elemFluxVarsCache[scvf];
+        const auto& sqrtK = fluxVarsCache.harmonicMeanSqrtPermeability();
+
+        // Obtain the Forchheimer coefficient from the spatial parameters
+        const Scalar forchCoeff = problem.spatialParams().forchCoeff(scvf);
+
+        // Initial guess of velocity: Darcy relation
+        DimWorldVector velocity = darcyVelocity;
+
+        DimWorldVector deltaV(0.0);           // the change in velocity between Newton iterations
+        DimWorldVector residual(10e10);  // the residual (function value that is to be minimized)
+        DimWorldMatrix  gradF(0.0);            // slope of equation that is to be solved
+
+        // Search by means of the Newton method for a root of Forchheimer equation
+        static const Scalar epsilon = getParamFromGroup<Scalar>(problem.paramGroup(), "Forchheimer.NewtonTolerance", 1e-12);
+        static const std::size_t maxNumIter = getParamFromGroup<std::size_t>(problem.paramGroup(), "Forchheimer.MaxIterations", 30);
+        for (int k = 0; residual.two_norm() > epsilon ; ++k)
+        {
+            if (k >= maxNumIter)
+                DUNE_THROW(NumericalProblem, "could not determine forchheimer velocity within "
+                                             << k << " iterations");
+
+            // calculate current value of Forchheimer equation
+            forchheimerResidual_(residual,
+                                 forchCoeff,
+                                 sqrtK,
+                                 darcyVelocity,
+                                 velocity,
+                                 elemVolVars,
+                                 scvf,
+                                 phaseIdx);
+
+            // newton's method: slope (gradF) and current value (residual) of function is known,
+            forchheimerDerivative_(gradF,
+                                   forchCoeff,
+                                   sqrtK,
+                                   velocity,
+                                   elemVolVars,
+                                   scvf,
+                                   phaseIdx);
+
+            // solve for change in velocity ("x-Axis intercept")
+            gradF.solve(deltaV, residual);
+            velocity -= deltaV;
+        }
+
+        // The fluxvariables expect a value on which an upwinding of the mobility is performed.
+        // We therefore divide by the mobility here.
+        const Scalar forchheimerUpwindMobility = doUpwind_(scvf, elemVolVars, upwindTerm,
+                                                           !std::signbit(velocity * scvf.unitOuterNormal()) /*insideIsUpstream*/);
+
+        // Do not divide by zero. If the mobility is zero, the resulting flux with upwinding will be zero anyway.
+        if (forchheimerUpwindMobility > 0.0)
+            velocity /= forchheimerUpwindMobility;
+
+        return velocity;
+    }
+
+     //! calculate the residual of the Forchheimer equation
+     template<class ElementVolumeVariables>
+     static void forchheimerResidual_(DimWorldVector& residual,
+                                      const Scalar forchCoeff,
+                                      const DimWorldMatrix& sqrtK,
+                                      const DimWorldVector& darcyVelocity,
+                                      const DimWorldVector& oldVelocity,
+                                      const ElementVolumeVariables& elemVolVars,
+                                      const SubControlVolumeFace& scvf,
+                                      const int phaseIdx)
+     {
+         residual = oldVelocity;
+
+         // residual += k_r/mu  K grad p
+         residual -= darcyVelocity;
+
+         // residual += c_F rho / mu abs(v) sqrt(K) v
+         auto upwindTerm = [phaseIdx](const auto& volVars){ return volVars.density(phaseIdx) / volVars.viscosity(phaseIdx); };
+         bool insideIsUpstream = !std::signbit(oldVelocity * scvf.unitOuterNormal());
+         const Scalar rhoOverMu = doUpwind_(scvf, elemVolVars, upwindTerm, insideIsUpstream);
+         sqrtK.usmv(forchCoeff * rhoOverMu * oldVelocity.two_norm(), oldVelocity, residual);
+     }
+
+     //! The analytical derivative of the the Forcheimer equation's residual
+    template<class ElementVolumeVariables>
+    static void forchheimerDerivative_(DimWorldMatrix& derivative,
+                                       const Scalar forchCoeff,
+                                       const DimWorldMatrix& sqrtK,
+                                       const DimWorldVector& velocity,
+                                       const ElementVolumeVariables& elemVolVars,
+                                       const SubControlVolumeFace& scvf,
+                                       const int phaseIdx)
+    {
+
+
+        // Initialize because for low velocities, we add and do not set the entries.
+        derivative = 0.0;
+
+        auto upwindTerm = [phaseIdx](const auto& volVars){ return volVars.density(phaseIdx) / volVars.viscosity(phaseIdx); };
+        bool insideIsUpstream = !std::signbit(velocity * scvf.unitOuterNormal());
+
+        const Scalar absV = velocity.two_norm() ;
+        // This part of the derivative is only calculated if the velocity is sufficiently small:
+        // do not divide by zero!
+        // This should not be very bad because the derivative is only intended to give an
+        // approximation of the gradient of the
+        // function that goes into the Newton scheme.
+        // This is important in the case of a one-phase region in two-phase flow. The non-existing
+        // phase has a velocity of zero (kr=0).
+        // f = sqrtK * vTimesV (this is  a matrix)
+        // f *= forchCoeff density / |velocity| / viscosity (multiply all entries with scalars)
+        const Scalar rhoOverMu = doUpwind_(scvf, elemVolVars, upwindTerm, insideIsUpstream);
+        if (absV > 1e-20)
+        {
+            for (int i = 0; i < dim; i++)
+            {
+                for (int k = 0; k <= i; k++)
+                {
+                    derivative[i][k] = sqrtK[i][i] * velocity[i] * velocity[k] * forchCoeff
+                    / absV  * rhoOverMu;
+
+                    if (k < i)
+                        derivative[k][i] = derivative[i][k];
+                }
+            }
+        }
+
+        // add on the main diagonal:
+        // 1 + sqrtK_i forchCoeff density |v| / viscosity
+        for (int i = 0; i < dim; i++)
+            derivative[i][i] += (1.0 + (sqrtK[i][i]*forchCoeff * absV * rhoOverMu));
+    }
+
+     template<class ElementVolumeVariables, class UpwindTermFunction>
+     static Scalar doUpwind_(const SubControlVolumeFace& scvf,
+                             const ElementVolumeVariables& elemVolVars,
+                             const UpwindTermFunction& upwindTerm,
+                             const bool insideIsUpstream)
+     {
+         static const Scalar upwindWeight = getParam<Scalar>("Implicit.UpwindWeight");
+
+         const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()];
+         const auto& outsideVolVars = elemVolVars[scvf.outsideScvIdx()];
+
+         if (!insideIsUpstream) // if sign of flux is negative
+             return (upwindWeight*upwindTerm(outsideVolVars)
+                          + (1.0 - upwindWeight)*upwindTerm(insideVolVars));
+         else
+             return (upwindWeight*upwindTerm(insideVolVars)
+                          + (1.0 - upwindWeight)*upwindTerm(outsideVolVars));
+     }
+
+    /*!
+     * \brief Check whether all off-diagonal entries of a tensor are zero.
+     *
+     * \param K the tensor that is to be checked.
+     *
+     * \return True if all off-diagonals are zero.
+     *
+    */
+    static bool isDiagonal_(const DimWorldMatrix & K)
+    {
+        using std::abs;
+        for (int i = 0; i < dim; i++) {
+            for (int k = 0; k < dim; k++) {
+                if ((i != k) && (abs(K[i][k]) >= 1e-25)) {
+                  return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    template<class Problem, class VolumeVariables,
+             std::enable_if_t<!Problem::SpatialParams::evaluatePermeabilityAtScvfIP(), int> = 0>
+    static decltype(auto) getPermeability_(const Problem& problem,
+                                           const VolumeVariables& volVars,
+                                           const GlobalPosition& scvfIpGlobal)
+    { return volVars.permeability(); }
+
+    template<class Problem, class VolumeVariables,
+             std::enable_if_t<Problem::SpatialParams::evaluatePermeabilityAtScvfIP(), int> = 0>
+    static decltype(auto) getPermeability_(const Problem& problem,
+                                           const VolumeVariables& volVars,
+                                           const GlobalPosition& scvfIpGlobal)
+    { return problem.spatialParams().permeabilityAtPos(scvfIpGlobal); }
+};
+
+/*!
+ * \ingroup CCTpfaDiscretization
+ * \brief Specialization of the CCTpfaForchheimersLaw grids where dim<dimWorld
+ */
+template<class ScalarType, class FVGridGeometry>
+class CCTpfaForchheimersLaw<ScalarType, FVGridGeometry, /*isNetwork*/ true>
+{
+    static_assert(AlwaysFalse<ScalarType>::value, "Forchheimer not implemented for network grids");
+};
+
+} // end namespace Dumux
+
+#endif
diff --git a/dumux/discretization/forchheimerslaw.hh b/dumux/discretization/forchheimerslaw.hh
new file mode 100644
index 0000000000..8d4de49360
--- /dev/null
+++ b/dumux/discretization/forchheimerslaw.hh
@@ -0,0 +1,55 @@
+// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+// vi: set et ts=4 sw=4 sts=4:
+/*****************************************************************************
+ *   See the file COPYING for full copying permissions.                      *
+ *                                                                           *
+ *   This program 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 2 of the License, or       *
+ *   (at your option) any later version.                                     *
+ *                                                                           *
+ *   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ *****************************************************************************/
+/*!
+ * \file
+ * \ingroup Discretization
+ * \brief Forchheimer's law specialized for different discretization schemes
+ *        This file contains the data which is required to calculate
+ *        volume and mass fluxes of fluid phases over a face of a finite volume by means
+ *        of the Forchheimer approximation. Specializations are provided for the different discretization methods.
+ */
+#ifndef DUMUX_DISCRETIZATION_FORCHHEIMERS_LAW_HH
+#define DUMUX_DISCRETIZATION_FORCHHEIMERS_LAW_HH
+
+#include <dumux/common/properties.hh>
+#include <dumux/discretization/methods.hh>
+
+namespace Dumux
+{
+// forward declaration
+template <class TypeTag, DiscretizationMethod discMethod>
+class ForchheimersLawImplementation
+{
+    static_assert(discMethod == DiscretizationMethod::cctpfa, "Forchheimer only implemented for cctpfa!");
+};
+
+/*!
+ * \ingroup Discretization
+ * \brief Evaluates the normal component of the Forchheimer velocity on a (sub)control volume face.
+ * \note Specializations are provided for the different discretization methods.
+ * These specializations are found in the headers included below.
+ */
+template <class TypeTag>
+using ForchheimersLaw = ForchheimersLawImplementation<TypeTag, GET_PROP_TYPE(TypeTag, FVGridGeometry)::discMethod>;
+
+} // end namespace Dumux
+
+#include <dumux/discretization/cellcentered/tpfa/forchheimerslaw.hh>
+
+#endif
-- 
GitLab