version 3.11-dev
Loading...
Searching...
No Matches
quantityreconstruction.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//
12
13#ifndef DUMUX_TWOPVE_QUANTITY_RECONSTRUCTION_HH
14#define DUMUX_TWOPVE_QUANTITY_RECONSTRUCTION_HH
15
16#include <algorithm>
17#include <array>
18#include <cmath>
19
20#include <dune/common/exceptions.hh>
21
24
25namespace Dumux {
26
27namespace TwoPVE {
28
33template<typename Scalar>
35{
36 Scalar current;
37 Scalar minimum;
38};
39
44template<typename Scalar>
46{
47 Scalar wetting;
48 Scalar nonwetting;
49};
50
55template<typename Scalar>
57{
58 Scalar wetting;
59 Scalar nonwetting;
60};
61
66template<typename Scalar>
68{
69 Scalar wetting;
70 Scalar nonwetting;
71};
72
77template<typename Scalar>
83
84} // end namespace TwoPVE
85
98template<class Scalar, class FluidSystem>
100{
101 static constexpr int wettingPhaseIdx = FluidSystem::phase0Idx;
102 static constexpr int nonwettingPhaseIdx = FluidSystem::phase1Idx;
103 static constexpr int numPhases = FluidSystem::numPhases;
104
105 using GasPlumeDistances = TwoPVE::GasPlumeDistancesData<Scalar>;
106 using PhaseDensities = TwoPVE::PhaseDensitiesData<Scalar>;
107 using PhaseViscosities = TwoPVE::PhaseViscositiesData<Scalar>;
108 using ResidualSaturations = TwoPVE::ResidualSaturationsData<Scalar>;
109 using BrooksCoreyParameters = TwoPVE::BrooksCoreyParametersData<Scalar>;
110
111public:
112
132 Scalar computeGasPlumeDist(const PhaseDensities& densities,
133 const ResidualSaturations& residualSaturations,
134 Scalar gravityNorm,
135 Scalar domainHeight,
136 Scalar saturationWCoarse,
137 Scalar minGasPlumeDist,
138 const BrooksCoreyParameters& brooksCoreyParameters) const
139 {
140 const Scalar densityW = densities.wetting;
141 const Scalar densityNw = densities.nonwetting;
142 const Scalar swr = residualSaturations.wetting;
143 const Scalar snr = residualSaturations.nonwetting;
144
145 if (gravityNorm == 0.0)
146 DUNE_THROW(Dune::InvalidStateException, "The two-phase vertical-equilibrium model requires nonzero gravity because its reconstruction assumes gravity-driven vertical segregation");
147
148 if (densityW <= densityNw)
149 DUNE_THROW(Dune::InvalidStateException, "The two-phase VE reconstruction requires the wetting phase to be denser than the nonwetting phase. Got rho_w=" << densityW << " and rho_n=" << densityNw);
150
151 // residual of the water balance: water content of the column minus the coarse-level water content,
152 // monotonically increasing in the gas plume distance
153 using std::max; using std::min;
154 const auto massConservation = [&](const Scalar gasPlumeDist)
155 {
156 const Scalar bottomOfMobileGas = max(gasPlumeDist, 0.0);
157 const Scalar waterBelowMinimum = max(min(gasPlumeDist, minGasPlumeDist), 0.0);
158 const Scalar waterTrappedRegion = (1.0-snr) * max(bottomOfMobileGas - max(minGasPlumeDist, 0.0), 0.0);
159 const Scalar waterAbovePlume = integrateSaturationWAbovePlume_(bottomOfMobileGas, domainHeight, gasPlumeDist, densities, residualSaturations, gravityNorm, brooksCoreyParameters);
160 return waterBelowMinimum + waterTrappedRegion + waterAbovePlume - saturationWCoarse * domainHeight;
161 };
162
163 if (massConservation(domainHeight) <= 0.0)
164 return domainHeight;
165
166 // the water content approaches the residual water content as the gas plume distance decreases below the column
167 if (saturationWCoarse <= swr)
168 DUNE_THROW(NumericalProblem, "The coarse-level wetting-phase saturation " << saturationWCoarse << " does not exceed the residual saturation " << swr);
169
170 // move the lower end of the bracket downwards until the residual is no longer positive
171 const Scalar fringeHeight = brooksCoreyParameters.entryPressure/((densityW - densityNw)*gravityNorm);
172 Scalar lowerBound = 0.0;
173 Scalar residualAtLowerBound = massConservation(lowerBound);
174 for (Scalar expansion = fringeHeight; residualAtLowerBound > 0.0; expansion *= 2.0)
175 {
176 lowerBound -= expansion;
177 residualAtLowerBound = massConservation(lowerBound);
178 }
179
180 if (residualAtLowerBound == 0.0)
181 return lowerBound;
182
183 return findScalarRootBrent(lowerBound, domainHeight, massConservation);
184 }
185
194 Scalar computeCapillaryPressureCoarse(Scalar gasPlumeDist,
195 const PhaseDensities& densities,
196 Scalar gravityNorm,
197 Scalar entryPressureBC) const
198 {
199 return gravityNorm * gasPlumeDist * (densities.nonwetting - densities.wetting) + entryPressureBC;
200 }
201
212 std::array<Scalar, numPhases> reconstructPressure(Scalar gasPlumeDist,
213 const PhaseDensities& densities,
214 Scalar gravityNorm,
215 Scalar heightAboveBottom,
216 Scalar pressureWCoarse,
217 Scalar entryPressureBC) const
218 {
219 const Scalar referenceDensityW = densities.wetting;
220 const Scalar referenceDensityNw = densities.nonwetting;
221
222 // the nonwetting-phase pressure below the gas plume exceeds the wetting-phase pressure by the entry pressure (capillary fringe model)
223 std::array<Scalar, numPhases> reconstructedPressures;
224 reconstructedPressures[wettingPhaseIdx] = pressureWCoarse - referenceDensityW * gravityNorm * heightAboveBottom;
225 if (heightAboveBottom <= gasPlumeDist)
226 reconstructedPressures[nonwettingPhaseIdx] = reconstructedPressures[wettingPhaseIdx] + entryPressureBC;
227 else
228 reconstructedPressures[nonwettingPhaseIdx] = pressureWCoarse - referenceDensityW * gravityNorm * gasPlumeDist - referenceDensityNw * gravityNorm * (heightAboveBottom - gasPlumeDist) + entryPressureBC;
229
230 return reconstructedPressures;
231 }
232
246 Scalar reconstructSaturation(const GasPlumeDistances& gasPlumeDistances,
247 const PhaseDensities& densities,
248 const ResidualSaturations& residualSaturations,
249 Scalar gravityNorm,
250 Scalar heightAboveBottom,
251 Scalar deltaZ,
252 const BrooksCoreyParameters& brooksCoreyParameters) const
253 {
254 const Scalar gasPlumeDist = gasPlumeDistances.current;
255 const Scalar minGasPlumeDist = gasPlumeDistances.minimum;
256
257 const Scalar lowerBound = heightAboveBottom - deltaZ/2.0;
258 const Scalar upperBound = heightAboveBottom + deltaZ/2.0;
259 const Scalar cellHeight = upperBound - lowerBound;
260
261 const Scalar snr = residualSaturations.nonwetting;
262 const Scalar saturationWBelowPlume = 1.0;
263 const Scalar saturationWTrappedRegion = 1.0 - snr;
264 const auto integrateSaturationWAbovePlume = [&](const Scalar lower, const Scalar upper)
265 {
266 return integrateSaturationWAbovePlume_(lower, upper, gasPlumeDist, densities, residualSaturations, gravityNorm, brooksCoreyParameters);
267 };
268
269 Scalar saturationIntegral = 0.0;
270
271 // differentiate between six cases:
272 // 1) fine-level element is completely under the gas plume,
273 // 2) fine-level element is completely above the gas plume,
274 // 3) fine-level element is completely in trapped gas region
275 // 4) fine-level element intersects only historical minimum plume distance
276 // 5) fine-level element intersects only the current gas plume distance
277 // 6) fine-level element intersects both plume distances
278
279 if (upperBound <= minGasPlumeDist) // 1)
280 saturationIntegral = saturationWBelowPlume*cellHeight;
281 else if (lowerBound >= gasPlumeDist) // 2)
282 saturationIntegral = integrateSaturationWAbovePlume(lowerBound, upperBound);
283 else if (lowerBound >= minGasPlumeDist && upperBound <= gasPlumeDist) // 3)
284 saturationIntegral = saturationWTrappedRegion*cellHeight;
285 else if (lowerBound < minGasPlumeDist && upperBound <= gasPlumeDist) // 4)
286 {
287 const Scalar integralBelowMinimum = saturationWBelowPlume * (minGasPlumeDist - lowerBound);
288 const Scalar integralTrappedRegion = saturationWTrappedRegion * (upperBound - minGasPlumeDist);
289 saturationIntegral = integralBelowMinimum + integralTrappedRegion;
290 }
291 else if (lowerBound >= minGasPlumeDist && upperBound > gasPlumeDist) // 5)
292 {
293 const Scalar integralTrappedRegion = saturationWTrappedRegion * (gasPlumeDist - lowerBound);
294 const Scalar integralAbovePlume = integrateSaturationWAbovePlume(gasPlumeDist, upperBound);
295 saturationIntegral = integralTrappedRegion + integralAbovePlume;
296 }
297 else // 6)
298 {
299 const Scalar integralBelowMinimum = saturationWBelowPlume * (minGasPlumeDist - lowerBound);
300 const Scalar integralTrappedRegion = saturationWTrappedRegion * (gasPlumeDist - minGasPlumeDist);
301 const Scalar integralAbovePlume = integrateSaturationWAbovePlume(gasPlumeDist, upperBound);
302 saturationIntegral = integralBelowMinimum + integralTrappedRegion + integralAbovePlume;
303 }
304
305 return saturationIntegral/cellHeight;
306 }
307
317 Scalar reconstructCapillaryPressure(Scalar gasPlumeDist,
318 const PhaseDensities& densities,
319 Scalar gravityNorm,
320 Scalar heightAboveBottom,
321 Scalar entryPressureBC) const
322 {
323 const Scalar referenceDensityW = densities.wetting;
324 const Scalar referenceDensityNw = densities.nonwetting;
325
326 if (heightAboveBottom <= gasPlumeDist)
327 return entryPressureBC;
328 else
329 return referenceDensityW * gravityNorm * (heightAboveBottom - gasPlumeDist) + entryPressureBC - referenceDensityNw * gravityNorm * (heightAboveBottom - gasPlumeDist);
330 }
331
343 std::array<Scalar, numPhases> reconstructMobilitiesFine(const GasPlumeDistances& gasPlumeDistances,
344 const PhaseDensities& densities,
345 const PhaseViscosities& viscosities,
346 Scalar gravityNorm,
347 Scalar heightAboveBottom,
348 Scalar deltaZ,
349 const BrooksCoreyParameters& brooksCoreyParameters) const
350 {
351 const Scalar gasPlumeDist = gasPlumeDistances.current;
352 const Scalar lowerBound = heightAboveBottom - deltaZ/2.0;
353 const Scalar upperBound = heightAboveBottom + deltaZ/2.0;
354
355 const Scalar relPermWBelowPlume = 1.0;
356 const Scalar relPermNwBelowPlume = 0.0;
357
358 // Brooks-Corey relative permeabilities in terms of the effective saturation Se = u^(-lambda):
359 // krw = Se^(2/lambda + 3) and krn = (1 - Se)^2 (1 - Se^(2/lambda + 1)), expanded into powers of u
360 const Scalar lambdaBC = brooksCoreyParameters.lambda;
361 const auto integratePower = [&](const Scalar exponent, const Scalar lower, const Scalar upper)
362 {
363 return integratePowerAbovePlume_(exponent, lower, upper, gasPlumeDist, densities, gravityNorm, brooksCoreyParameters.entryPressure);
364 };
365 const auto integrateRelPermWAbovePlume = [&](const Scalar lower, const Scalar upper)
366 {
367 return integratePower(2.0 + 3.0*lambdaBC, lower, upper);
368 };
369 const auto integrateRelPermNwAbovePlume = [&](const Scalar lower, const Scalar upper)
370 {
371 const Scalar integral = (upper - lower)
372 - 2.0*integratePower(lambdaBC, lower, upper)
373 + integratePower(2.0*lambdaBC, lower, upper)
374 - integratePower(2.0 + lambdaBC, lower, upper)
375 + 2.0*integratePower(2.0 + 2.0*lambdaBC, lower, upper)
376 - integratePower(2.0 + 3.0*lambdaBC, lower, upper);
377
378 // close to the gas plume distance, where krn vanishes, the terms cancel up to round-off errors of either sign
379 using std::max;
380 return max(integral, 0.0);
381 };
382
383 // the relative permeabilities are piecewise defined, with a kink or jump at the gas plume distance
384 const auto integrateRelativePermeability = [&](const auto& integralAbovePlume, const Scalar relPermBelowPlume)
385 {
386 const Scalar cellHeight = upperBound - lowerBound;
387
388 // differentiate between three cases:
389 // 1) fine-level element is completely under the gas plume,
390 // 2) fine-level element is completely above the gas plume,
391 // 3) fine-level element intersects with gas plume
392 // for zp_min<z<zp, there might be snr present and the rel perm for gas is treated as 0, thus the check z<zp is enough
393 if (upperBound <= gasPlumeDist)
394 return relPermBelowPlume * cellHeight;
395 else if (lowerBound >= gasPlumeDist)
396 return integralAbovePlume(lowerBound, upperBound);
397 else
398 return relPermBelowPlume * (gasPlumeDist - lowerBound) + integralAbovePlume(gasPlumeDist, upperBound);
399 };
400
401 // cell averages of the relative permeabilities divided by the viscosities
402 std::array<Scalar, numPhases> mobilities;
403 mobilities[wettingPhaseIdx] = integrateRelativePermeability(integrateRelPermWAbovePlume, relPermWBelowPlume)/(upperBound - lowerBound)/viscosities.wetting;
404 mobilities[nonwettingPhaseIdx] = integrateRelativePermeability(integrateRelPermNwAbovePlume, relPermNwBelowPlume)/(upperBound - lowerBound)/viscosities.nonwetting;
405 return mobilities;
406 }
407
408private:
461 Scalar integratePowerAbovePlume_(Scalar exponent,
462 Scalar lowerBound,
463 Scalar upperBound,
464 Scalar gasPlumeDist,
465 const PhaseDensities& densities,
466 Scalar gravityNorm,
467 Scalar entryPressureBC) const
468 {
469 using std::expm1; using std::log1p; using std::pow;
470 const Scalar lengthScale = entryPressureBC/((densities.wetting - densities.nonwetting)*gravityNorm);
471 const Scalar uLower = 1.0 + (lowerBound - gasPlumeDist)/lengthScale;
472 const Scalar logRatio = log1p((upperBound - lowerBound)/(lengthScale*uLower));
473 const Scalar k = 1.0 - exponent;
474 if (k == 0.0)
475 return lengthScale*logRatio;
476
477 // expm1 keeps the antiderivative accurate for exponents close to one, where it approaches the logarithm
478 return lengthScale*pow(uLower, k)*expm1(k*logRatio)/k;
479 }
480
492 Scalar integrateSaturationWAbovePlume_(Scalar lowerBound,
493 Scalar upperBound,
494 Scalar gasPlumeDist,
495 const PhaseDensities& densities,
496 const ResidualSaturations& residualSaturations,
497 Scalar gravityNorm,
498 const BrooksCoreyParameters& brooksCoreyParameters) const
499 {
500 const Scalar swr = residualSaturations.wetting;
501 const Scalar snr = residualSaturations.nonwetting;
502 const Scalar integralEffectiveSaturation = integratePowerAbovePlume_(brooksCoreyParameters.lambda, lowerBound, upperBound, gasPlumeDist, densities, gravityNorm, brooksCoreyParameters.entryPressure);
503 return swr*(upperBound - lowerBound) + (1.0 - swr - snr)*integralEffectiveSaturation;
504 }
505};
506
507} // end namespace Dumux
508
509#endif
Exception thrown if a fixable numerical problem occurs.
Definition exceptions.hh:27
Reconstructs the fine-level quantities of a column from its coarse-level quantities assuming vertical...
Definition quantityreconstruction.hh:100
Scalar reconstructSaturation(const GasPlumeDistances &gasPlumeDistances, const PhaseDensities &densities, const ResidualSaturations &residualSaturations, Scalar gravityNorm, Scalar heightAboveBottom, Scalar deltaZ, const BrooksCoreyParameters &brooksCoreyParameters) const
Reconstructs the fine-level wetting-phase saturation as a mean integral of the saturation across the ...
Definition quantityreconstruction.hh:246
Scalar reconstructCapillaryPressure(Scalar gasPlumeDist, const PhaseDensities &densities, Scalar gravityNorm, Scalar heightAboveBottom, Scalar entryPressureBC) const
Reconstructs the fine-level capillary pressure.
Definition quantityreconstruction.hh:317
std::array< Scalar, numPhases > reconstructPressure(Scalar gasPlumeDist, const PhaseDensities &densities, Scalar gravityNorm, Scalar heightAboveBottom, Scalar pressureWCoarse, Scalar entryPressureBC) const
Reconstructs the fine-level wetting-phase and nonwetting-phase pressures.
Definition quantityreconstruction.hh:212
Scalar computeGasPlumeDist(const PhaseDensities &densities, const ResidualSaturations &residualSaturations, Scalar gravityNorm, Scalar domainHeight, Scalar saturationWCoarse, Scalar minGasPlumeDist, const BrooksCoreyParameters &brooksCoreyParameters) const
Computes the gas plume distance for a coarse column, which is the height of the lower boundary of the...
Definition quantityreconstruction.hh:132
std::array< Scalar, numPhases > reconstructMobilitiesFine(const GasPlumeDistances &gasPlumeDistances, const PhaseDensities &densities, const PhaseViscosities &viscosities, Scalar gravityNorm, Scalar heightAboveBottom, Scalar deltaZ, const BrooksCoreyParameters &brooksCoreyParameters) const
Reconstructs the fine-level wetting-phase and nonwetting-phase mobilities.
Definition quantityreconstruction.hh:343
Scalar computeCapillaryPressureCoarse(Scalar gasPlumeDist, const PhaseDensities &densities, Scalar gravityNorm, Scalar entryPressureBC) const
Computes the capillary pressure on the coarse level.
Definition quantityreconstruction.hh:194
Some exceptions thrown in DuMux.
Root finding algorithms for scalar functions.
Scalar findScalarRootBrent(Scalar a, Scalar b, const ResFunc &residual, const Scalar tol=1e-13, const int maxIter=200)
Brent's root finding algorithm for scalar functions.
Definition findscalarroot.hh:99
Definition quantityreconstruction.hh:27
Definition adapt.hh:17
The parameters of the Brooks-Corey material law.
Definition quantityreconstruction.hh:79
Scalar entryPressure
Definition quantityreconstruction.hh:81
Scalar lambda
Definition quantityreconstruction.hh:80
The gas plume distance of a column and its minimum over all previous time steps.
Definition quantityreconstruction.hh:35
Scalar minimum
Definition quantityreconstruction.hh:37
Scalar current
Definition quantityreconstruction.hh:36
The densities of the wetting and the nonwetting phase.
Definition quantityreconstruction.hh:46
Scalar wetting
Definition quantityreconstruction.hh:47
Scalar nonwetting
Definition quantityreconstruction.hh:48
The viscosities of the wetting and the nonwetting phase.
Definition quantityreconstruction.hh:57
Scalar wetting
Definition quantityreconstruction.hh:58
Scalar nonwetting
Definition quantityreconstruction.hh:59
The residual saturations of the wetting and the nonwetting phase.
Definition quantityreconstruction.hh:68
Scalar wetting
Definition quantityreconstruction.hh:69
Scalar nonwetting
Definition quantityreconstruction.hh:70