version 3.11-dev
velocity.hh
Go to the documentation of this file.
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-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5// SPDX-License-Identifier: GPL-3.0-or-later
6//
13#ifndef DUMUX_POROUSMEDIUMFLOW_VELOCITY_HH
14#define DUMUX_POROUSMEDIUMFLOW_VELOCITY_HH
15
16#include <vector>
17#include <type_traits>
18
19#include <dune/common/fvector.hh>
20#include <dune/common/float_cmp.hh>
21#include <dune/common/reservedvector.hh>
22#include <dune/istl/bvector.hh>
23#include <dune/geometry/type.hh>
24#include <dune/geometry/referenceelements.hh>
25
30#include <dumux/flux/traits.hh>
31
32namespace Dumux {
33
34#ifndef DOXYGEN
35namespace Detail {
36// helper structs and functions detecting if the model is compositional
37
38template <typename T, typename ...Ts>
39using MoleFractionDetector = decltype(std::declval<T>().moleFraction(std::declval<Ts>()...));
40
41template<class T, typename ...Args>
42static constexpr bool hasMoleFraction()
43{ return Dune::Std::is_detected<MoleFractionDetector, T, Args...>::value; }
44
45template <typename T, typename ...Ts>
46using MassFractionDetector = decltype(std::declval<T>().massFraction(std::declval<Ts>()...));
47
48template<class T, typename ...Args>
49static constexpr bool hasMassFraction()
50{ return Dune::Std::is_detected<MassFractionDetector, T, Args...>::value; }
51
52} // end namespace Detail
53#endif // DOXYGEN
54
59template<class GridVariables, class FluxVariables>
61{
62 using GridGeometry = typename GridVariables::GridGeometry;
63 using Extrusion = Extrusion_t<GridGeometry>;
64 using FVElementGeometry = typename GridGeometry::LocalView;
65 using SubControlVolume = typename GridGeometry::SubControlVolume;
66 using SubControlVolumeFace = typename GridGeometry::SubControlVolumeFace;
67 using GridView = typename GridGeometry::GridView;
68 using Element = typename GridView::template Codim<0>::Entity;
69 using GridVolumeVariables = typename GridVariables::GridVolumeVariables;
70 using ElementFluxVarsCache = typename GridVariables::GridFluxVariablesCache::LocalView;
71 using VolumeVariables = typename GridVariables::VolumeVariables;
72 using ElementVolumeVariables = typename GridVolumeVariables::LocalView;
73 using FluidSystem = typename VolumeVariables::FluidSystem;
74 using Scalar = typename GridVariables::Scalar;
75 using FluxTraits = typename Dumux::FluxTraits<FluxVariables>;
76 using AdvectionType = typename FluxVariables::AdvectionType;
77
78 static constexpr int dim = GridView::dimension;
79 static constexpr int dimWorld = GridView::dimensionworld;
80 static constexpr bool isBox = GridGeometry::discMethod == DiscretizationMethods::box;
81 static constexpr bool isDiamond = GridGeometry::discMethod == DiscretizationMethods::fcdiamond;
82 static constexpr bool stationaryVelocityField = FluxTraits::hasStationaryVelocityField();
83
84 using GlobalPosition = typename Element::Geometry::GlobalCoordinate;
85
86 using Problem = typename GridVolumeVariables::Problem;
87
88 static constexpr bool modelIsCompositional = Detail::hasMoleFraction<typename GridVolumeVariables::VolumeVariables, int, int>() ||
89 Detail::hasMassFraction<typename GridVolumeVariables::VolumeVariables, int, int>();
90
91 static_assert(VolumeVariables::numFluidPhases() >= 1, "Velocity output only makes sense for models with fluid phases.");
92
93 using Velocity = Dune::FieldVector<Scalar, dimWorld>;
94 using ReferenceElementVelocity = Dune::FieldVector<Scalar, dim>;
95public:
96 static constexpr int numFluidPhases = VolumeVariables::numFluidPhases();
97 using VelocityVector = std::vector<Velocity>;
98
104 PorousMediumFlowVelocity(const GridVariables& gridVariables)
105 : problem_(gridVariables.curGridVolVars().problem())
106 , gridGeometry_(gridVariables.gridGeometry())
107 , gridVariables_(gridVariables)
108 {
109 // set the number of scvs the vertices are connected to
110 if constexpr (isBox && dim > 1)
111 {
112 // resize to the number of vertices of the grid
113 cellNum_.assign(gridGeometry_.gridView().size(dim), 0);
114
115 for (const auto& element : elements(gridGeometry_.gridView()))
116 for (unsigned int vIdx = 0; vIdx < element.subEntities(dim); ++vIdx)
117 ++cellNum_[gridGeometry_.vertexMapper().subIndex(element, vIdx, dim)];
118 }
119 }
120
124 const Element& element,
125 const FVElementGeometry& fvGeometry,
126 const ElementVolumeVariables& elemVolVars,
127 const ElementFluxVarsCache& elemFluxVarsCache,
128 int phaseIdx) const
129 {
130 const auto geometry = element.geometry();
131 const Dune::GeometryType geomType = geometry.type();
132
133 // the upwind term to be used for the volume flux evaluation
134 auto upwindTerm = [phaseIdx](const auto& volVars) { return volVars.mobility(phaseIdx); };
135
136 if constexpr (isBox && dim == 1)
137 {
138 Velocity tmpVelocity(0.0);
139 tmpVelocity = (geometry.corner(1) - geometry.corner(0));
140 tmpVelocity /= tmpVelocity.two_norm();
141
142 for (auto&& scvf : scvfs(fvGeometry))
143 {
144 if (scvf.boundary())
145 continue;
146
147 // instantiate the flux variables
148 FluxVariables fluxVars;
149 fluxVars.init(problem_, element, fvGeometry, elemVolVars, scvf, elemFluxVarsCache);
150
151 // get the volume flux divided by the area of the
152 // subcontrolvolume face in the reference element
153 Scalar localArea = scvfReferenceArea_(geomType, scvf.index());
154 Scalar flux = fluxVars.advectiveFlux(phaseIdx, upwindTerm) / localArea;
155 const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()];
156 flux /= insideVolVars.extrusionFactor() * Extrusion::area(fvGeometry, scvf) / scvf.area();
157 tmpVelocity *= flux;
158
159 const int eIdxGlobal = gridGeometry_.elementMapper().index(element);
160 velocity[eIdxGlobal] = tmpVelocity;
161 }
162 return;
163 }
164
165 // get the transposed Jacobian of the element mapping
166 using Dune::referenceElement;
167 const auto localPos = referenceElement(geometry).position(0, 0);
168 const auto jacobianT2 = geometry.jacobianTransposed(localPos);
169
170 if constexpr (isBox)
171 {
172 using ScvVelocities = Dune::BlockVector<Velocity>;
173 ScvVelocities scvVelocities(fvGeometry.numScv());
174 scvVelocities = 0;
175
176 for (auto&& scvf : scvfs(fvGeometry))
177 {
178 if (scvf.boundary())
179 continue;
180
181 // local position of integration point
182 const auto localPosIP = geometry.local(scvf.ipGlobal());
183
184 // Transformation of the global normal vector to normal vector in the reference element
185 const auto jacobianT1 = geometry.jacobianTransposed(localPosIP);
186 const auto globalNormal = scvf.unitOuterNormal();
187 GlobalPosition localNormal(0);
188 jacobianT1.mv(globalNormal, localNormal);
189 localNormal /= localNormal.two_norm();
190
191 // instantiate the flux variables
192 FluxVariables fluxVars;
193 fluxVars.init(problem_, element, fvGeometry, elemVolVars, scvf, elemFluxVarsCache);
194
195 // get the volume flux divided by the area of the
196 // subcontrolvolume face in the reference element
197 Scalar localArea = scvfReferenceArea_(geomType, scvf.index());
198 Scalar flux = fluxVars.advectiveFlux(phaseIdx, upwindTerm) / localArea;
199 const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()];
200 flux /= insideVolVars.extrusionFactor() * Extrusion::area(fvGeometry, scvf) / scvf.area();
201
202 // transform the volume flux into a velocity vector
203 Velocity tmpVelocity = localNormal;
204 tmpVelocity *= flux;
205
206 scvVelocities[scvf.insideScvIdx()] += tmpVelocity;
207 scvVelocities[scvf.outsideScvIdx()] += tmpVelocity;
208 }
209
210 // transform vertex velocities from local to global coordinates
211 for (auto&& scv : scvs(fvGeometry))
212 {
213 int vIdxGlobal = scv.dofIndex();
214
215 // calculate the subcontrolvolume velocity by the Piola transformation
216 Velocity scvVelocity(0);
217
218 jacobianT2.mtv(scvVelocities[scv.indexInElement()], scvVelocity);
219 scvVelocity /= geometry.integrationElement(localPos)*cellNum_[vIdxGlobal];
220 // add up the wetting phase subcontrolvolume velocities for each vertex
221 velocity[vIdxGlobal] += scvVelocity;
222 }
223 }
224 else if constexpr (isDiamond)
225 DUNE_THROW(Dune::NotImplemented, "Velocity output with diamond discretization");
226 else
227 {
228 // For the number of scvfs per facet (mpfa) we simply obtain the number of
229 // corners of the first facet as prisms/pyramids are not supported here anyway
230 // -> for prisms/pyramids the number of scvfs would differ from facet to facet
231 static constexpr bool isMpfa = GridGeometry::discMethod == DiscretizationMethods::ccmpfa;
232 const int numScvfsPerFace = isMpfa ? element.template subEntity<1>(0).geometry().corners() : 1;
233
234 if (fvGeometry.numScvf() != element.subEntities(1)*numScvfsPerFace)
235 DUNE_THROW(Dune::NotImplemented, "Velocity output for non-conforming grids");
236
237 if (!geomType.isCube() && !geomType.isSimplex())
238 DUNE_THROW(Dune::NotImplemented, "Velocity output for other geometry types than cube and simplex");
239
240 // first we extract the corner indices for each scv for the CIV method
241 // for network grids there might be multiple intersection with the same geometryInInside
242 // we identify those by the indexInInside for now (assumes conforming grids at branching facets)
243 // here we keep track of them
244 std::vector<bool> handledScvf;
245 if (dim < dimWorld)
246 handledScvf.resize(element.subEntities(1), false);
247
248 // find the local face indices of the scvfs (for conforming meshes)
249 std::vector<unsigned int> scvfIndexInInside(fvGeometry.numScvf());
250 int localScvfIdx = 0;
251 for (const auto& intersection : intersections(gridGeometry_.gridView(), element))
252 {
253 if (dim < dimWorld)
254 if (handledScvf[intersection.indexInInside()])
255 continue;
256
257 if (intersection.neighbor() || intersection.boundary())
258 {
259 for (int i = 0; i < numScvfsPerFace; ++i)
260 scvfIndexInInside[localScvfIdx++] = intersection.indexInInside();
261
262 // for surface and network grids mark that we handled this face
263 if (dim < dimWorld)
264 handledScvf[intersection.indexInInside()] = true;
265 }
266 }
267
268 Dune::ReservedVector<Scalar, 2*dim> scvfFluxes(element.subEntities(1), 0.0);
269 localScvfIdx = 0;
270 for (auto&& scvf : scvfs(fvGeometry))
271 {
272 const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()];
273 if (!scvf.boundary())
274 {
275 FluxVariables fluxVars;
276 fluxVars.init(problem_, element, fvGeometry, elemVolVars, scvf, elemFluxVarsCache);
277 scvfFluxes[scvfIndexInInside[localScvfIdx]] += fluxVars.advectiveFlux(phaseIdx, upwindTerm)/insideVolVars.extrusionFactor();
278 }
279 else
280 {
281 auto bcTypes = problem_.boundaryTypes(element, scvf);
282 if (bcTypes.hasOnlyDirichlet())
283 {
284 FluxVariables fluxVars;
285 fluxVars.init(problem_, element, fvGeometry, elemVolVars, scvf, elemFluxVarsCache);
286 scvfFluxes[scvfIndexInInside[localScvfIdx]] += fluxVars.advectiveFlux(phaseIdx, upwindTerm)/insideVolVars.extrusionFactor();
287 }
288 }
289
290 // increment scvf counter
291 localScvfIdx++;
292 }
293
294 // Correct boundary fluxes in case of Neumann conditions.
295 // In this general setting, it would be very difficult to
296 // calculate correct phase, i.e., volume, fluxes from arbitrary
297 // Neumann conditions. We approximate the Neumann flux by the
298 // flux on the opposite face. For extremely distorted grids this can
299 // lead to unexpected results (but then TPFA also leads to unexpected results).
300 localScvfIdx = 0;
301 for (auto&& scvf : scvfs(fvGeometry))
302 {
303 if (scvf.boundary())
304 {
305 auto bcTypes = problem_.boundaryTypes(element, scvf);
306 if (bcTypes.hasNeumann())
307 {
308 // for stationary velocity fields we can easily compute the correct velocity
309 // this special treatment makes sure that the velocity field is also correct on Neumann boundaries
310 // of tracer problems where the velocity field is given.
311 // (For Dirichlet boundaries no special treatment is necessary.)
312 if (stationaryVelocityField)
313 {
314 const auto flux = AdvectionType::flux(problem_, element, fvGeometry, elemVolVars,
315 scvf, phaseIdx, elemFluxVarsCache);
316
317 const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()];
318 scvfFluxes[scvfIndexInInside[localScvfIdx]] += flux / insideVolVars.extrusionFactor();
319 }
320 else
321 {
322 // check if we have Neumann no flow, we can just use 0
323 const auto neumannFlux = problem_.neumann(element, fvGeometry, elemVolVars, elemFluxVarsCache, scvf);
324 using NumEqVector = std::decay_t<decltype(neumannFlux)>;
325 if (Dune::FloatCmp::eq<NumEqVector, Dune::FloatCmp::CmpStyle::absolute>(neumannFlux, NumEqVector(0.0), 1e-30))
326 scvfFluxes[scvfIndexInInside[localScvfIdx]] = 0;
327
328 // otherwise, we try some reconstruction
329 // for cubes
330 else if (dim == 1 || geomType.isCube())
331 {
332 const auto fIdx = scvfIndexInInside[localScvfIdx];
333
334 if constexpr (!modelIsCompositional)
335 {
336 // We assume that the density at the face equals the one at the cell center and reconstruct a volume flux from the Neumann mass flux.
337 const auto& insideVolVars = elemVolVars[scvf.insideScvIdx()];
338 const auto eqIdx = VolumeVariables::Indices::conti0EqIdx + phaseIdx;
339 scvfFluxes[fIdx] += neumannFlux[eqIdx] / insideVolVars.density(phaseIdx) * scvf.area();
340 }
341 else
342 {
343 // For compositional models, we generally can't reconstruct the volume flow from the Neumann flux (which is a species flux rather
344 // than a phase flux here). Instead, we use the velocity of the opposing face.
345 const auto fIdxOpposite = fIdx%2 ? fIdx-1 : fIdx+1;
346 scvfFluxes[fIdx] = -scvfFluxes[fIdxOpposite];
347 }
348 }
349
350 // for simplices
351 else if (geomType.isSimplex())
352 scvfFluxes[scvfIndexInInside[localScvfIdx]] = 0;
353
354 else
355 DUNE_THROW(Dune::NotImplemented, "Velocity computation at Neumann boundaries for cell-centered and prism/pyramid");
356 }
357 }
358 }
359
360 // increment scvf counter
361 localScvfIdx++;
362 }
363
364
365 ReferenceElementVelocity refVelocity;
366 // cubes: On the reference element simply average over opposite fluxes
367 // note that this is equal to a corner velocity interpolation method
368 if (dim == 1 || geomType.isCube())
369 {
370 assert(scvfFluxes.size() == 2*dim);
371 for (int i = 0; i < dim; ++i)
372 refVelocity[i] = 0.5 * (scvfFluxes[2*i + 1] - scvfFluxes[2*i]);
373 }
374
375 // simplices: Raviart-Thomas-0 interpolation evaluated at the cell center
376 else if (geomType.isSimplex())
377 {
378 assert(scvfFluxes.size() == dim+1);
379 for (int i = 0; i < dim; ++i)
380 {
381 refVelocity[i] = -scvfFluxes[dim - 1 - i];
382 for (int fIdx = 0; fIdx < dim + 1; ++fIdx)
383 refVelocity[i] += scvfFluxes[fIdx]/(dim + 1);
384 }
385 }
386
387 // 3D prism and pyramids
388 else
389 DUNE_THROW(Dune::NotImplemented, "Velocity computation for cell-centered and prism/pyramid");
390
391 Velocity scvVelocity(0);
392 jacobianT2.mtv(refVelocity, scvVelocity);
393
394 scvVelocity /= geometry.integrationElement(localPos);
395
396 int eIdxGlobal = gridGeometry_.elementMapper().index(element);
397
398 velocity[eIdxGlobal] = scvVelocity;
399
400 } // cell-centered
401 }
402
403private:
404 // The area of a subcontrolvolume face in a reference element.
405 // The 3d non-cube values have been calculated with quadrilateralArea3D
406 // of boxfvelementgeometry.hh.
407 static Scalar scvfReferenceArea_(Dune::GeometryType geomType, int fIdx)
408 {
409 if (dim == 1 || geomType.isCube())
410 {
411 return 1.0/(1 << (dim-1));
412 }
413 else if (geomType.isTriangle())
414 {
415 static const Scalar faceToArea[] = {0.372677996249965,
416 0.372677996249965,
417 0.235702260395516};
418 return faceToArea[fIdx];
419 }
420 else if (geomType.isTetrahedron())
421 {
422 static const Scalar faceToArea[] = {0.102062072615966,
423 0.102062072615966,
424 0.058925565098879,
425 0.102062072615966,
426 0.058925565098879,
427 0.058925565098879};
428 return faceToArea[fIdx];
429 }
430 else if (geomType.isPyramid())
431 {
432 static const Scalar faceToArea[] = {0.130437298687488,
433 0.130437298687488,
434 0.130437298687488,
435 0.130437298687488,
436 0.150923085635624,
437 0.1092906420717,
438 0.1092906420717,
439 0.0781735959970571};
440 return faceToArea[fIdx];
441 }
442 else if (geomType.isPrism())
443 {
444 static const Scalar faceToArea[] = {0.166666666666667,
445 0.166666666666667,
446 0.166666666666667,
447 0.186338998124982,
448 0.186338998124982,
449 0.117851130197758,
450 0.186338998124982,
451 0.186338998124982,
452 0.117851130197758};
453 return faceToArea[fIdx];
454 }
455 else {
456 DUNE_THROW(Dune::NotImplemented, "scvf area for unknown GeometryType");
457 }
458 }
459
460private:
461 const Problem& problem_;
462 const GridGeometry& gridGeometry_;
463 const GridVariables& gridVariables_;
464
465 std::vector<int> cellNum_;
466};
467
468} // end namespace Dumux
469
470#endif
Velocity computation for implicit (porous media) models.
Definition: velocity.hh:61
void calculateVelocity(VelocityVector &velocity, const Element &element, const FVElementGeometry &fvGeometry, const ElementVolumeVariables &elemVolVars, const ElementFluxVarsCache &elemFluxVarsCache, int phaseIdx) const
Definition: velocity.hh:123
PorousMediumFlowVelocity(const GridVariables &gridVariables)
Constructor initializes the static data with the initial solution.
Definition: velocity.hh:104
std::vector< Velocity > VelocityVector
Definition: velocity.hh:97
static constexpr int numFluidPhases
Definition: velocity.hh:96
Element solution classes and factory functions.
Helper classes to compute the integration elements.
Defines the flux traits.
typename NumEqVectorTraits< PrimaryVariables >::type NumEqVector
A vector with the same size as numbers of equations This is the default implementation and has to be ...
Definition: numeqvector.hh:34
The available discretization methods in Dumux.
constexpr CCMpfa ccmpfa
Definition: method.hh:146
constexpr FCDiamond fcdiamond
Definition: method.hh:152
constexpr Box box
Definition: method.hh:147
Definition: adapt.hh:17
typename Extrusion< T >::type Extrusion_t
Convenience alias for obtaining the extrusion type.
Definition: extrusion.hh:166
std::ranges::range auto scvs(const FVElementGeometry &fvGeometry, const LocalDof &localDof)
Definition: localdof.hh:79
The infrastructure to retrieve run-time parameters from Dune::ParameterTrees.
Traits of a flux variables type.
Definition: flux/traits.hh:32
static constexpr bool hasStationaryVelocityField()
Definition: flux/traits.hh:33