blackoilnewtonmethod.hpp
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 This file is part of the Open Porous Media project (OPM).
5
6 OPM is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 2 of the License, or
9 (at your option) any later version.
10
11 OPM is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with OPM. If not, see <http://www.gnu.org/licenses/>.
18
19 Consult the COPYING file in the top-level source directory of this
20 module for the precise wording of the license and the list of
21 copyright holders.
22*/
28#ifndef OPM_BLACK_OIL_NEWTON_METHOD_HPP
29#define OPM_BLACK_OIL_NEWTON_METHOD_HPP
30
31#include <opm/common/Exceptions.hpp>
32
36
38
40
41#include <opm/material/common/Valgrind.hpp>
42
43#include <algorithm>
44#include <cmath>
45#include <limits>
46#include <vector>
47
48namespace Opm::Properties {
49
50template <class TypeTag, class MyTypeTag>
51struct DiscNewtonMethod;
52
53} // namespace Opm::Properties
54
55namespace Opm {
56
62template <class TypeTag>
63class BlackOilNewtonMethod : public GetPropType<TypeTag, Properties::DiscNewtonMethod>
64{
75 static constexpr bool enableBioeffects = getPropValue<TypeTag, Properties::EnableBioeffects>();
76 using BioeffectsModule = BlackOilBioeffectsModule<TypeTag, enableBioeffects>;
77
78 static const unsigned numEq = getPropValue<TypeTag, Properties::NumEq>();
79 static constexpr bool enableSaltPrecipitation = getPropValue<TypeTag, Properties::EnableSaltPrecipitation>();
80
81public:
82 explicit BlackOilNewtonMethod(Simulator& simulator) : ParentType(simulator)
83 {
84 bparams_.read();
85 }
86
91 {
92 ParentType::finishInit();
93
94 wasSwitched_.resize(this->model().numTotalDof(), false);
95 }
96
98 {
99 // Preserve the switching history across retries of this timestep.
100 wasSwitchedAtStepStart_ = wasSwitched_;
101 }
102
104 {
105 wasSwitched_ = wasSwitchedAtStepStart_;
106 }
107
111 static void registerParameters()
112 {
113 ParentType::registerParameters();
115 }
116
121 unsigned numPriVarsSwitched() const
122 { return numPriVarsSwitched_; }
123
124protected:
127
132 {
133 numPriVarsSwitched_ = 0;
134 ParentType::beginIteration_();
135 }
136
143 void endIteration_(SolutionVector& uCurrentIter,
144 const SolutionVector& uLastIter)
145 {
146#if HAVE_MPI
147 // in the MPI enabled case we need to add up the number of DOF
148 // for which the interpretation changed over all processes.
149 const int localSwitched = numPriVarsSwitched_;
150 MPI_Allreduce(&localSwitched,
151 &numPriVarsSwitched_,
152 /*num=*/1,
153 MPI_INT,
154 MPI_SUM,
155 MPI_COMM_WORLD);
156#endif // HAVE_MPI
157
158 this->simulator_.model().newtonMethod().endIterMsg()
159 << ", num switched=" << numPriVarsSwitched_;
160
161 ParentType::endIteration_(uCurrentIter, uLastIter);
162 }
163
164public:
167 bool wasSwitched(const unsigned globalDofIdx) const
168 { return wasSwitched_[globalDofIdx]; }
169
170 void setWasSwitched(const unsigned globalDofIdx, const bool switched)
171 { wasSwitched_[globalDofIdx] = switched; }
172
173 void update_(SolutionVector& nextSolution,
174 const SolutionVector& currentSolution,
175 const GlobalEqVector& solutionUpdate,
176 const GlobalEqVector& currentResidual)
177 {
178 const auto& comm = this->simulator_.gridView().comm();
179
180 int succeeded;
181 try {
182 ParentType::update_(nextSolution,
183 currentSolution,
184 solutionUpdate,
185 currentResidual);
186 succeeded = 1;
187 }
188 catch (...) {
189 succeeded = 0;
190 }
191 succeeded = comm.min(succeeded);
192
193 if (!succeeded) {
194 throw NumericalProblem("A process did not succeed in adapting the primary variables");
195 }
196
197 numPriVarsSwitched_ = comm.sum(numPriVarsSwitched_);
198 }
199
200 template <class DofIndices>
201 void update_(SolutionVector& nextSolution,
202 const SolutionVector& currentSolution,
203 const GlobalEqVector& solutionUpdate,
204 const GlobalEqVector& currentResidual,
205 const DofIndices& dofIndices)
206 {
207 const auto zero = 0.0 * solutionUpdate[0];
208 for (auto dofIdx : dofIndices) {
209 if (solutionUpdate[dofIdx] == zero) {
210 continue;
211 }
213 nextSolution[dofIdx],
214 currentSolution[dofIdx],
215 solutionUpdate[dofIdx],
216 currentResidual[dofIdx]);
217 }
218 }
219
220protected:
224 void updatePrimaryVariables_(unsigned globalDofIdx,
225 PrimaryVariables& nextValue,
226 const PrimaryVariables& currentValue,
227 const EqVector& update,
228 const EqVector& currentResidual)
229 {
230 static constexpr bool enableSolvent =
231 Indices::solventSaturationIdx != std::numeric_limits<unsigned>::max();
232 static constexpr bool enableExtbo =
233 Indices::zFractionIdx != std::numeric_limits<unsigned>::max();
234 static constexpr bool enablePolymer =
235 Indices::polymerConcentrationIdx != std::numeric_limits<unsigned>::max();
236 static constexpr bool enablePolymerWeight =
237 Indices::polymerMoleWeightIdx != std::numeric_limits<unsigned>::max();
238 static constexpr bool enableFullyImplicitThermal =
239 Indices::temperatureIdx != std::numeric_limits<unsigned>::max();
240 static constexpr bool enableFoam =
241 Indices::foamConcentrationIdx != std::numeric_limits<unsigned>::max();
242 static constexpr bool enableBrine =
243 Indices::saltConcentrationIdx != std::numeric_limits<unsigned>::max();
244 static constexpr bool enableMICP = Indices::enableMICP;
245
246 currentValue.checkDefined();
247 Valgrind::CheckDefined(update);
248 Valgrind::CheckDefined(currentResidual);
249
250 // saturation delta for each phase
251 Scalar deltaSw = 0.0;
252 Scalar deltaSo = 0.0;
253 Scalar deltaSg = 0.0;
254 Scalar deltaSs = 0.0;
255
256 if (currentValue.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw)
257 {
258 if constexpr (Indices::waterSwitchIdx != std::numeric_limits<unsigned>::max()) {
259 deltaSw = update[Indices::waterSwitchIdx];
260 deltaSo -= deltaSw;
261 }
262 }
263 if (currentValue.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg)
264 {
265 if constexpr (Indices::compositionSwitchIdx != std::numeric_limits<unsigned>::max()) {
266 deltaSg = update[Indices::compositionSwitchIdx];
267 deltaSo -= deltaSg;
268 }
269 }
270 if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) {
271 if constexpr (Indices::solventSaturationIdx != std::numeric_limits<unsigned>::max()) {
272 deltaSs = update[Indices::solventSaturationIdx];
273 deltaSo -= deltaSs;
274 }
275 }
276
277 // maximum saturation delta
278 Scalar maxSatDelta = std::max(std::abs(deltaSg), std::abs(deltaSo));
279 maxSatDelta = std::max(maxSatDelta, std::abs(deltaSw));
280 maxSatDelta = std::max(maxSatDelta, std::abs(deltaSs));
281
282 // scaling factor for saturation deltas to make sure that none of them exceeds
283 // the specified threshold value.
284 Scalar satAlpha = 1.0;
285 if (maxSatDelta > bparams_.dsMax_) {
286 satAlpha = bparams_.dsMax_ / maxSatDelta;
287 }
288
289 for (unsigned pvIdx = 0; pvIdx < numEq; ++pvIdx) {
290 // calculate the update of the current primary variable. For the black-oil
291 // model we limit the pressure delta relative to the pressure's current
292 // absolute value (Default: 30%) and saturation deltas to an absolute change
293 // (Default: 20%). Further, we ensure that the R factors, solvent
294 // "saturation" and polymer concentration do not become negative after the
295 // update.
296 Scalar delta = update[pvIdx];
297
298 // limit pressure delta
299 if (pvIdx == Indices::pressureSwitchIdx) {
300 if (std::abs(delta) > bparams_.dpMaxRel_ * currentValue[pvIdx]) {
301 delta = signum(delta) * bparams_.dpMaxRel_ * currentValue[pvIdx];
302 }
303 }
304 // water saturation delta
305 else if (pvIdx == Indices::waterSwitchIdx)
306 if (currentValue.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw) {
307 delta *= satAlpha;
308 }
309 else {
310 //Ensure Rvw and Rsw factor does not become negative
311 if (delta > currentValue[ Indices::waterSwitchIdx]) {
312 delta = currentValue[ Indices::waterSwitchIdx];
313 }
314 }
315 else if (pvIdx == Indices::compositionSwitchIdx) {
316 // the switching primary variable for composition is tricky because the
317 // "reasonable" value ranges it exhibits vary widely depending on its
318 // interpretation since it can represent Sg, Rs or Rv. For now, we only
319 // limit saturation deltas and ensure that the R factors do not become
320 // negative.
321 if (currentValue.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg) {
322 delta *= satAlpha;
323 }
324 else {
325 // Ensure Rv and Rs factor does not become negative
326 if (delta > currentValue[Indices::compositionSwitchIdx]) {
327 delta = currentValue[Indices::compositionSwitchIdx];
328 }
329 }
330 }
331 else if (enableSolvent && pvIdx == Indices::solventSaturationIdx) {
332 // solvent saturation updates are also subject to the Appleyard chop
333 if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) {
334 delta *= satAlpha;
335 }
336 else {
337 // Ensure Rssolw factor does not become negative
338 if (delta > currentValue[Indices::solventSaturationIdx]) {
339 delta = currentValue[Indices::solventSaturationIdx];
340 }
341 }
342 }
343 else if (enableExtbo && pvIdx == Indices::zFractionIdx) {
344 // z fraction updates are also subject to the Appleyard chop
345 const auto& curr = currentValue[Indices::zFractionIdx]; // or currentValue[pvIdx] given the block condition
346 delta = std::clamp(delta, curr - Scalar{1.0}, curr);
347 }
348 else if (enablePolymerWeight && pvIdx == Indices::polymerMoleWeightIdx) {
349 const double sign = delta >= 0. ? 1. : -1.;
350 // maximum change of polymer molecular weight, the unit is MDa.
351 // applying this limit to stabilize the simulation. The value itself is still experimental.
352 const Scalar maxMolarWeightChange = 100.0;
353 delta = sign * std::min(std::abs(delta), maxMolarWeightChange);
354 delta *= satAlpha;
355 }
356 else if (enableFullyImplicitThermal && pvIdx == Indices::temperatureIdx) {
357 const double sign = delta >= 0. ? 1. : -1.;
358 delta = sign * std::min(std::abs(delta), bparams_.maxTempChange_);
359 }
360 else if (enableBrine && pvIdx == Indices::saltConcentrationIdx &&
361 enableSaltPrecipitation &&
362 currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp)
363 {
364 const Scalar maxSaltSaturationChange = 0.1;
365 const Scalar sign = delta >= 0. ? 1. : -1.;
366 delta = sign * std::min(std::abs(delta), maxSaltSaturationChange);
367 }
368
369 // do the actual update
370 nextValue[pvIdx] = currentValue[pvIdx] - delta;
371
372 // keep the solvent saturation between 0 and 1
373 if (enableSolvent && pvIdx == Indices::solventSaturationIdx) {
374 if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) {
375 nextValue[pvIdx] = std::min(std::max(nextValue[pvIdx], Scalar{0.0}), Scalar{1.0});
376 }
377 }
378
379 // keep the z fraction between 0 and 1
380 if (enableExtbo && pvIdx == Indices::zFractionIdx) {
381 nextValue[pvIdx] = std::min(std::max(nextValue[pvIdx], Scalar{0.0}), Scalar{1.0});
382 }
383
384 // keep the polymer concentration above 0
385 if (enablePolymer && pvIdx == Indices::polymerConcentrationIdx) {
386 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
387 }
388
389 if (enablePolymerWeight && pvIdx == Indices::polymerMoleWeightIdx) {
390 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
391 const double polymerConcentration = nextValue[Indices::polymerConcentrationIdx];
392 if (polymerConcentration < 1.e-10) {
393 nextValue[pvIdx] = 0.0;
394 }
395 }
396
397 // keep the foam concentration above 0
398 if (enableFoam && pvIdx == Indices::foamConcentrationIdx) {
399 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
400 }
401
402 if (enableBrine && pvIdx == Indices::saltConcentrationIdx) {
403 // keep the salt concentration above 0
404 if (!enableSaltPrecipitation ||
405 currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Cs)
406 {
407 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
408 }
409 // keep the salt saturation below upperlimit
410 if (enableSaltPrecipitation &&
411 currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp)
412 {
413 nextValue[pvIdx] = std::min(nextValue[pvIdx], Scalar{1.0-1.e-8});
414 }
415 }
416
417 // keep the temperature within given values
418 if (enableFullyImplicitThermal && pvIdx == Indices::temperatureIdx) {
419 nextValue[pvIdx] = std::clamp(nextValue[pvIdx], bparams_.tempMin_, bparams_.tempMax_);
420 }
421
422 if (pvIdx == Indices::pressureSwitchIdx) {
423 nextValue[pvIdx] = std::clamp(nextValue[pvIdx], bparams_.pressMin_, bparams_.pressMax_);
424 }
425
426 // keep the values above 0
427 // for the biofilm and calcite, we set an upper limit equal to the initial porosity
428 // minus 1e-8. This prevents singularities (e.g., one of the calcite source term is
429 // evaluated at 1/(iniPoro - calcite)). The value 1e-8 is taken from the salt precipitation
430 // clapping above.
431 if constexpr (enableBioeffects) {
432 if (pvIdx == Indices::microbialConcentrationIdx) {
433 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
434 }
435 if (pvIdx == Indices::biofilmVolumeFractionIdx) {
436 nextValue[pvIdx] = std::clamp(nextValue[pvIdx],
437 Scalar{0.0},
438 this->problem().referencePorosity(globalDofIdx, 0) - 1e-8);
439 }
440 if constexpr (enableMICP) {
441 if (pvIdx == Indices::oxygenConcentrationIdx) {
442 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
443 }
444 if (pvIdx == Indices::ureaConcentrationIdx) {
445 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
446 }
447 if (pvIdx == Indices::calciteVolumeFractionIdx) {
448 nextValue[pvIdx] = std::clamp(nextValue[pvIdx], Scalar{0.0},
449 this->problem().referencePorosity(globalDofIdx, 0) - 1e-8);
450 }
451 }
452 }
453 }
454
455 // switch the new primary variables to something which is physically meaningful.
456 // use a threshold value after a switch to make it harder to switch back
457 // immediately.
458 if (wasSwitched_[globalDofIdx]) {
459 wasSwitched_[globalDofIdx] = nextValue.adaptPrimaryVariables(this->problem(),
460 globalDofIdx,
461 bparams_.waterSaturationMax_,
462 bparams_.waterOnlyThreshold_,
463 bparams_.priVarOscilationThreshold_);
464 }
465 else {
466 wasSwitched_[globalDofIdx] = nextValue.adaptPrimaryVariables(this->problem(),
467 globalDofIdx,
468 bparams_.waterSaturationMax_,
469 bparams_.waterOnlyThreshold_);
470 }
471
472 if (wasSwitched_[globalDofIdx]) {
473 ++numPriVarsSwitched_;
474 }
475 if (bparams_.projectSaturations_) {
476 nextValue.chopAndNormalizeSaturations();
477 }
478
479 nextValue.checkDefined();
480 }
481
482private:
483 int numPriVarsSwitched_{};
484
485 BlackoilNewtonParams<Scalar> bparams_{};
486
487 // keep track of cells where the primary variable meaning has changed
488 // to detect and hinder oscillations
489 std::vector<bool> wasSwitched_{};
490 std::vector<bool> wasSwitchedAtStepStart_{};
491};
492
493} // namespace Opm
494
495#endif // OPM_BLACK_OIL_NEWTHON_METHOD_HPP
Contains classes extending the black-oil model. \detail This file holds dummy definitions,...
Declares the properties required by the black oil model.
A newton solver which is specific to the black oil model.
Definition: blackoilnewtonmethod.hpp:64
void beginIteration_()
Indicates the beginning of a Newton iteration.
Definition: blackoilnewtonmethod.hpp:131
bool wasSwitched(const unsigned globalDofIdx) const
Definition: blackoilnewtonmethod.hpp:167
static void registerParameters()
Register all run-time parameters for the blackoil newton method.
Definition: blackoilnewtonmethod.hpp:111
void update_(SolutionVector &nextSolution, const SolutionVector &currentSolution, const GlobalEqVector &solutionUpdate, const GlobalEqVector &currentResidual)
Definition: blackoilnewtonmethod.hpp:173
void finishInit()
Finialize the construction of the object.
Definition: blackoilnewtonmethod.hpp:90
void endIteration_(SolutionVector &uCurrentIter, const SolutionVector &uLastIter)
Indicates that one Newton iteration was finished.
Definition: blackoilnewtonmethod.hpp:143
unsigned numPriVarsSwitched() const
Returns the number of degrees of freedom for which the interpretation has changed for the most recent...
Definition: blackoilnewtonmethod.hpp:121
BlackOilNewtonMethod(Simulator &simulator)
Definition: blackoilnewtonmethod.hpp:82
void updatePrimaryVariables_(unsigned globalDofIdx, PrimaryVariables &nextValue, const PrimaryVariables &currentValue, const EqVector &update, const EqVector &currentResidual)
Update a single primary variables object.
Definition: blackoilnewtonmethod.hpp:224
friend ParentType
Definition: blackoilnewtonmethod.hpp:126
void setWasSwitched(const unsigned globalDofIdx, const bool switched)
Definition: blackoilnewtonmethod.hpp:170
void restorePrimaryVariableSwitches()
Definition: blackoilnewtonmethod.hpp:103
void capturePrimaryVariableSwitches()
Definition: blackoilnewtonmethod.hpp:97
void update_(SolutionVector &nextSolution, const SolutionVector &currentSolution, const GlobalEqVector &solutionUpdate, const GlobalEqVector &currentResidual, const DofIndices &dofIndices)
Definition: blackoilnewtonmethod.hpp:201
The multi-dimensional Newton method.
Definition: newtonmethod.hh:100
Definition: blackoilmodel.hh:74
Definition: blackoilbioeffectsmodules.hh:45
constexpr int signum(Scalar val)
Template function which returns the sign of a floating point value.
Definition: signum.hh:41
typename Properties::Detail::GetPropImpl< TypeTag, Property >::type::type GetPropType
get the type alias defined in the property (equivalent to old macro GET_PROP_TYPE(....
Definition: propertysystem.hh:233
static void registerParameters()
Registers the parameters in parameter system.