GCC Code Coverage Report


Directory: ../../../builds/dumux-repositories/
File: /builds/dumux-repositories/dumux/dumux/flux/forchheimervelocity.hh
Date: 2024-09-21 20:52:54
Exec Total Coverage
Lines: 58 60 96.7%
Functions: 8 16 50.0%
Branches: 49 82 59.8%

Line Branch Exec Source
1 // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 // vi: set et ts=4 sw=4 sts=4:
3 //
4 // SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7 /*!
8 * \file
9 * \ingroup Flux
10 * \brief Forchheimer's law
11 * This file contains the calculation of the Forchheimer velocity
12 * for a given Darcy velocity
13 */
14 #ifndef DUMUX_FLUX_FORCHHEIMER_VELOCITY_HH
15 #define DUMUX_FLUX_FORCHHEIMER_VELOCITY_HH
16
17 #include <dune/common/fvector.hh>
18 #include <dune/common/fmatrix.hh>
19
20 #include <dumux/common/math.hh>
21 #include <dumux/common/parameters.hh>
22 #include <dumux/common/properties.hh>
23 #include <dumux/common/typetraits/typetraits.hh>
24
25 #include <dumux/flux/facetensoraverage.hh>
26
27 namespace Dumux {
28
29 /*!
30 * \ingroup Flux
31 * \brief Forchheimer's law
32 * For a detailed description see dumux/flow/forchheimerslaw.hh
33 *
34 * This file contains the calculation of the Forchheimer velocity
35 * for a given Darcy velocity
36 */
37 template<class Scalar, class GridGeometry, class FluxVariables>
38 class ForchheimerVelocity
39 {
40 using FVElementGeometry = typename GridGeometry::LocalView;
41 using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace;
42 using GridView = typename GridGeometry::GridView;
43 using Element = typename GridView::template Codim<0>::Entity;
44
45 static constexpr int dim = GridView::dimension;
46 static constexpr int dimWorld = GridView::dimensionworld;
47 using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
48 public:
49 using UpwindScheme = typename FluxVariables::UpwindScheme;
50 using DimWorldVector = Dune::FieldVector<Scalar, dimWorld>;
51 using DimWorldMatrix = Dune::FieldMatrix<Scalar, dimWorld, dimWorld>;
52
53
54 template<class ElementVolumeVariables>
55 3568 static DimWorldVector velocity(const FVElementGeometry& fvGeometry,
56 const ElementVolumeVariables& elemVolVars,
57 const SubControlVolumeFace& scvf,
58 const int phaseIdx,
59 const DimWorldMatrix sqrtK,
60 const DimWorldVector darcyVelocity)
61 {
62 3568 const auto& problem = elemVolVars.gridVolVars().problem();
63
64 // Obtain the Forchheimer coefficient from the spatial parameters
65 3568 const Scalar forchCoeff = problem.spatialParams().forchCoeff(scvf);
66
67 // Initial guess of velocity: Darcy relation
68 3568 DimWorldVector velocity = darcyVelocity;
69
70 3568 DimWorldVector deltaV(0.0); // the change in velocity between Newton iterations
71 3568 DimWorldVector residual(10e10); // the residual (function value that is to be minimized)
72 3568 DimWorldMatrix gradF(0.0); // slope of equation that is to be solved
73
74 // Search by means of the Newton method for a root of Forchheimer equation
75
5/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3566 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
3568 static const Scalar epsilon = getParamFromGroup<Scalar>(problem.paramGroup(), "Forchheimer.NewtonTolerance", 1e-12);
76
5/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3566 times.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 2 times.
✗ Branch 10 not taken.
3568 static const std::size_t maxNumIter = getParamFromGroup<std::size_t>(problem.paramGroup(), "Forchheimer.MaxIterations", 30);
77
2/2
✓ Branch 0 taken 25004 times.
✓ Branch 1 taken 3568 times.
57144 for (int k = 0; residual.two_norm() > epsilon ; ++k)
78 {
79
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25004 times.
25004 if (k >= maxNumIter)
80 DUNE_THROW(NumericalProblem, "could not determine forchheimer velocity within "
81 << k << " iterations");
82
83 // calculate current value of Forchheimer equation
84 25004 forchheimerResidual_(residual,
85 forchCoeff,
86 sqrtK,
87 darcyVelocity,
88 velocity,
89 elemVolVars,
90 scvf,
91 phaseIdx);
92
93 // newton's method: slope (gradF) and current value (residual) of function is known,
94 25004 forchheimerDerivative_(gradF,
95 forchCoeff,
96 sqrtK,
97 velocity,
98 elemVolVars,
99 scvf,
100 phaseIdx);
101
102 // solve for change in velocity ("x-Axis intercept")
103 25004 gradF.solve(deltaV, residual);
104 25004 velocity -= deltaV;
105 }
106
107 // The fluxvariables expect a value on which an upwinding of the mobility is performed.
108 // We therefore divide by the mobility here.
109 14272 auto upwindTerm = [phaseIdx](const auto& volVars){ return volVars.mobility(phaseIdx); };
110 12912 const Scalar forchheimerUpwindMobility = UpwindScheme::multiplier(elemVolVars, scvf, upwindTerm, velocity * scvf.unitOuterNormal(), phaseIdx);
111
112 // Do not divide by zero. If the mobility is zero, the resulting flux with upwinding will be zero anyway.
113
1/2
✓ Branch 0 taken 3568 times.
✗ Branch 1 not taken.
3568 if (forchheimerUpwindMobility > 0.0)
114 velocity /= forchheimerUpwindMobility;
115
116 3568 return velocity;
117 }
118
119 /*! \brief Returns the harmonic mean of \f$\sqrt{K_0}\f$ and \f$\sqrt{K_1}\f$.
120 *
121 * This is a specialization for scalar-valued permeabilities which returns a tensor with identical diagonal entries.
122 */
123 template<class Problem, class ElementVolumeVariables,
124 bool scalarPerm = std::is_same<typename Problem::SpatialParams::PermeabilityType, Scalar>::value,
125 std::enable_if_t<scalarPerm, int> = 0>
126 4080 static DimWorldMatrix calculateHarmonicMeanSqrtPermeability(const Problem& problem,
127 const ElementVolumeVariables& elemVolVars,
128 const SubControlVolumeFace& scvf)
129 {
130 using std::sqrt;
131 4080 Scalar harmonicMeanSqrtK(0.0);
132
133
1/2
✓ Branch 0 taken 1360 times.
✗ Branch 1 not taken.
4080 const auto insideScvIdx = scvf.insideScvIdx();
134
1/2
✓ Branch 0 taken 1360 times.
✗ Branch 1 not taken.
4080 const auto& insideVolVars = elemVolVars[insideScvIdx];
135
3/4
✓ Branch 0 taken 3600 times.
✓ Branch 1 taken 480 times.
✓ Branch 2 taken 1360 times.
✗ Branch 3 not taken.
5440 const Scalar Ki = getPermeability_(problem, insideVolVars, scvf.ipGlobal());
136 4080 const Scalar sqrtKi = sqrt(Ki);
137
138 4080 if (!scvf.boundary())
139 {
140
1/2
✓ Branch 0 taken 1360 times.
✗ Branch 1 not taken.
3600 const auto outsideScvIdx = scvf.outsideScvIdx();
141
1/2
✓ Branch 0 taken 1360 times.
✗ Branch 1 not taken.
3600 const auto& outsideVolVars = elemVolVars[outsideScvIdx];
142 7200 const Scalar Kj = getPermeability_(problem, outsideVolVars, scvf.ipGlobal());
143 3600 const Scalar sqrtKj = sqrt(Kj);
144
1/2
✓ Branch 0 taken 3600 times.
✗ Branch 1 not taken.
3600 harmonicMeanSqrtK = faceTensorAverage(sqrtKi, sqrtKj, scvf.unitOuterNormal());
145 }
146 else
147 harmonicMeanSqrtK = sqrtKi;
148
149 // Convert to tensor
150 4080 DimWorldMatrix result(0.0);
151
2/2
✓ Branch 0 taken 8160 times.
✓ Branch 1 taken 4080 times.
12240 for (int i = 0; i < dimWorld; ++i)
152 24480 result[i][i] = harmonicMeanSqrtK;
153
154 4080 return result;
155 }
156
157 /*! \brief Returns the harmonic mean of \f$\sqrt{\mathbf{K_0}}\f$ and \f$\sqrt{\mathbf{K_1}}\f$.
158 *
159 * This is a specialization for tensor-valued permeabilities.
160 */
161 template<class Problem, class ElementVolumeVariables,
162 bool scalarPerm = std::is_same<typename Problem::SpatialParams::PermeabilityType, Scalar>::value,
163 std::enable_if_t<!scalarPerm, int> = 0>
164 static DimWorldMatrix calculateHarmonicMeanSqrtPermeability(const Problem& problem,
165 const ElementVolumeVariables& elemVolVars,
166 const SubControlVolumeFace& scvf)
167 {
168 using std::sqrt;
169 DimWorldMatrix sqrtKi(0.0);
170 DimWorldMatrix sqrtKj(0.0);
171 DimWorldMatrix harmonicMeanSqrtK(0.0);
172
173 const auto insideScvIdx = scvf.insideScvIdx();
174 const auto& insideVolVars = elemVolVars[insideScvIdx];
175 const auto& Ki = getPermeability_(problem, insideVolVars, scvf.ipGlobal());
176 // Make sure the permeability matrix does not have off-diagonal entries
177 // TODO implement method using eigenvalues and eigenvectors for general tensors
178 if (!isDiagonal_(Ki))
179 DUNE_THROW(Dune::NotImplemented, "Only diagonal permeability tensors are supported.");
180
181 for (int i = 0; i < dim; ++i)
182 sqrtKi[i][i] = sqrt(Ki[i][i]);
183
184 if (!scvf.boundary())
185 {
186 const auto outsideScvIdx = scvf.outsideScvIdx();
187 const auto& outsideVolVars = elemVolVars[outsideScvIdx];
188 const auto& Kj = getPermeability_(problem, outsideVolVars, scvf.ipGlobal());
189 // Make sure the permeability matrix does not have off-diagonal entries
190 if (!isDiagonal_(Kj))
191 DUNE_THROW(Dune::NotImplemented, "Only diagonal permeability tensors are supported.");
192
193 for (int i = 0; i < dim; ++i)
194 sqrtKj[i][i] = sqrt(Kj[i][i]);
195
196 harmonicMeanSqrtK = faceTensorAverage(sqrtKi, sqrtKj, scvf.unitOuterNormal());
197 }
198 else
199 harmonicMeanSqrtK = sqrtKi;
200
201 return harmonicMeanSqrtK;
202 }
203
204 private:
205
206 //! calculate the residual of the Forchheimer equation
207 template<class ElementVolumeVariables>
208 25004 static void forchheimerResidual_(DimWorldVector& residual,
209 const Scalar forchCoeff,
210 const DimWorldMatrix& sqrtK,
211 const DimWorldVector& darcyVelocity,
212 const DimWorldVector& oldVelocity,
213 const ElementVolumeVariables& elemVolVars,
214 const SubControlVolumeFace& scvf,
215 const int phaseIdx)
216 {
217 25004 residual = oldVelocity;
218 // residual += k_r/mu K grad p
219 25004 residual -= darcyVelocity;
220 // residual += c_F rho / mu abs(v) sqrt(K) v
221 150024 auto upwindTerm = [phaseIdx](const auto& volVars){ return volVars.density(phaseIdx) / volVars.viscosity(phaseIdx); };
222 75012 const Scalar rhoOverMu = UpwindScheme::multiplier(elemVolVars, scvf, upwindTerm, oldVelocity * scvf.unitOuterNormal(), phaseIdx);
223 50008 sqrtK.usmv(forchCoeff * rhoOverMu * oldVelocity.two_norm(), oldVelocity, residual);
224 25004 }
225
226 //! The analytical derivative of the the Forcheimer equation's residual
227 template<class ElementVolumeVariables>
228 25004 static void forchheimerDerivative_(DimWorldMatrix& derivative,
229 const Scalar forchCoeff,
230 const DimWorldMatrix& sqrtK,
231 const DimWorldVector& velocity,
232 const ElementVolumeVariables& elemVolVars,
233 const SubControlVolumeFace& scvf,
234 const int phaseIdx)
235 {
236 // Initialize because for low velocities, we add and do not set the entries.
237 25004 derivative = 0.0;
238
239 150024 auto upwindTerm = [phaseIdx](const auto& volVars){ return volVars.density(phaseIdx) / volVars.viscosity(phaseIdx); };
240
241 25004 const Scalar absV = velocity.two_norm();
242 // This part of the derivative is only calculated if the velocity is sufficiently small:
243 // do not divide by zero!
244 // This should not be very bad because the derivative is only intended to give an
245 // approximation of the gradient of the
246 // function that goes into the Newton scheme.
247 // This is important in the case of a one-phase region in two-phase flow. The non-existing
248 // phase has a velocity of zero (kr=0).
249 // f = sqrtK * vTimesV (this is a matrix)
250 // f *= forchCoeff density / |velocity| / viscosity (multiply all entries with scalars)
251 75012 const Scalar rhoOverMu = UpwindScheme::multiplier(elemVolVars, scvf, upwindTerm, velocity * scvf.unitOuterNormal(), phaseIdx);
252
2/2
✓ Branch 0 taken 24646 times.
✓ Branch 1 taken 358 times.
25004 if (absV > 1e-20)
253 {
254
2/2
✓ Branch 0 taken 49292 times.
✓ Branch 1 taken 24646 times.
73938 for (int i = 0; i < dim; i++)
255 {
256
2/2
✓ Branch 0 taken 73938 times.
✓ Branch 1 taken 49292 times.
123230 for (int k = 0; k <= i; k++)
257 {
258
10/10
✓ Branch 0 taken 24646 times.
✓ Branch 1 taken 49292 times.
✓ Branch 2 taken 24646 times.
✓ Branch 3 taken 49292 times.
✓ Branch 4 taken 24646 times.
✓ Branch 5 taken 49292 times.
✓ Branch 6 taken 24646 times.
✓ Branch 7 taken 49292 times.
✓ Branch 8 taken 24646 times.
✓ Branch 9 taken 49292 times.
369690 derivative[i][k] = sqrtK[i][i] * velocity[i] * velocity[k] * forchCoeff
259
2/2
✓ Branch 0 taken 24646 times.
✓ Branch 1 taken 49292 times.
73938 / absV * rhoOverMu;
260
261
2/2
✓ Branch 0 taken 24646 times.
✓ Branch 1 taken 49292 times.
73938 if (k < i)
262 123230 derivative[k][i] = derivative[i][k];
263 }
264 }
265 }
266
267 // add on the main diagonal:
268 // 1 + sqrtK_i forchCoeff density |v| / viscosity
269
2/2
✓ Branch 0 taken 50008 times.
✓ Branch 1 taken 25004 times.
75012 for (int i = 0; i < dim; i++)
270 250040 derivative[i][i] += (1.0 + (sqrtK[i][i]*forchCoeff * absV * rhoOverMu));
271 25004 }
272
273 /*!
274 * \brief Check whether all off-diagonal entries of a tensor are zero.
275 *
276 * \param K the tensor that is to be checked.
277 *
278 * \return True if all off-diagonals are zero.
279 *
280 */
281 static bool isDiagonal_(const DimWorldMatrix & K)
282 {
283 using std::abs;
284 for (int i = 0; i < dim; i++)
285 for (int k = 0; k < dim; k++)
286 if ((i != k) && (abs(K[i][k]) >= 1e-25))
287 return false;
288 return true;
289 }
290
291 template<class Problem, class VolumeVariables>
292 static decltype(auto) getPermeability_(const Problem& problem,
293 const VolumeVariables& volVars,
294 const GlobalPosition& scvfIpGlobal)
295 {
296 if constexpr (Problem::SpatialParams::evaluatePermeabilityAtScvfIP())
297 return problem.spatialParams().permeabilityAtPos(scvfIpGlobal);
298 else
299
3/4
✓ Branch 0 taken 3600 times.
✓ Branch 1 taken 480 times.
✓ Branch 2 taken 3600 times.
✗ Branch 3 not taken.
6320 return volVars.permeability();
300 }
301 };
302
303 } // end namespace Dumux
304
305 #endif
306