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
100 static void registerParameters()
101 {
102 ParentType::registerParameters();
104 }
105
110 unsigned numPriVarsSwitched() const
111 { return numPriVarsSwitched_; }
112
113protected:
116
121 {
122 numPriVarsSwitched_ = 0;
123 ParentType::beginIteration_();
124 }
125
132 void endIteration_(SolutionVector& uCurrentIter,
133 const SolutionVector& uLastIter)
134 {
135#if HAVE_MPI
136 // in the MPI enabled case we need to add up the number of DOF
137 // for which the interpretation changed over all processes.
138 const int localSwitched = numPriVarsSwitched_;
139 MPI_Allreduce(&localSwitched,
140 &numPriVarsSwitched_,
141 /*num=*/1,
142 MPI_INT,
143 MPI_SUM,
144 MPI_COMM_WORLD);
145#endif // HAVE_MPI
146
147 this->simulator_.model().newtonMethod().endIterMsg()
148 << ", num switched=" << numPriVarsSwitched_;
149
150 ParentType::endIteration_(uCurrentIter, uLastIter);
151 }
152
153public:
154 void update_(SolutionVector& nextSolution,
155 const SolutionVector& currentSolution,
156 const GlobalEqVector& solutionUpdate,
157 const GlobalEqVector& currentResidual)
158 {
159 const auto& comm = this->simulator_.gridView().comm();
160
161 int succeeded;
162 try {
163 ParentType::update_(nextSolution,
164 currentSolution,
165 solutionUpdate,
166 currentResidual);
167 succeeded = 1;
168 }
169 catch (...) {
170 succeeded = 0;
171 }
172 succeeded = comm.min(succeeded);
173
174 if (!succeeded) {
175 throw NumericalProblem("A process did not succeed in adapting the primary variables");
176 }
177
178 numPriVarsSwitched_ = comm.sum(numPriVarsSwitched_);
179 }
180
181 template <class DofIndices>
182 void update_(SolutionVector& nextSolution,
183 const SolutionVector& currentSolution,
184 const GlobalEqVector& solutionUpdate,
185 const GlobalEqVector& currentResidual,
186 const DofIndices& dofIndices)
187 {
188 const auto zero = 0.0 * solutionUpdate[0];
189 for (auto dofIdx : dofIndices) {
190 if (solutionUpdate[dofIdx] == zero) {
191 continue;
192 }
194 nextSolution[dofIdx],
195 currentSolution[dofIdx],
196 solutionUpdate[dofIdx],
197 currentResidual[dofIdx]);
198 }
199 }
200
201protected:
205 void updatePrimaryVariables_(unsigned globalDofIdx,
206 PrimaryVariables& nextValue,
207 const PrimaryVariables& currentValue,
208 const EqVector& update,
209 const EqVector& currentResidual)
210 {
211 static constexpr bool enableSolvent =
212 Indices::solventSaturationIdx != std::numeric_limits<unsigned>::max();
213 static constexpr bool enableExtbo =
214 Indices::zFractionIdx != std::numeric_limits<unsigned>::max();
215 static constexpr bool enablePolymer =
216 Indices::polymerConcentrationIdx != std::numeric_limits<unsigned>::max();
217 static constexpr bool enablePolymerWeight =
218 Indices::polymerMoleWeightIdx != std::numeric_limits<unsigned>::max();
219 static constexpr bool enableFullyImplicitThermal =
220 Indices::temperatureIdx != std::numeric_limits<unsigned>::max();
221 static constexpr bool enableFoam =
222 Indices::foamConcentrationIdx != std::numeric_limits<unsigned>::max();
223 static constexpr bool enableBrine =
224 Indices::saltConcentrationIdx != std::numeric_limits<unsigned>::max();
225 static constexpr bool enableMICP = Indices::enableMICP;
226
227 currentValue.checkDefined();
228 Valgrind::CheckDefined(update);
229 Valgrind::CheckDefined(currentResidual);
230
231 // saturation delta for each phase
232 Scalar deltaSw = 0.0;
233 Scalar deltaSo = 0.0;
234 Scalar deltaSg = 0.0;
235 Scalar deltaSs = 0.0;
236
237 if (currentValue.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw)
238 {
239 if constexpr (Indices::waterSwitchIdx != std::numeric_limits<unsigned>::max()) {
240 deltaSw = update[Indices::waterSwitchIdx];
241 deltaSo -= deltaSw;
242 }
243 }
244 if (currentValue.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg)
245 {
246 if constexpr (Indices::compositionSwitchIdx != std::numeric_limits<unsigned>::max()) {
247 deltaSg = update[Indices::compositionSwitchIdx];
248 deltaSo -= deltaSg;
249 }
250 }
251 if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) {
252 if constexpr (Indices::solventSaturationIdx != std::numeric_limits<unsigned>::max()) {
253 deltaSs = update[Indices::solventSaturationIdx];
254 deltaSo -= deltaSs;
255 }
256 }
257
258 // maximum saturation delta
259 Scalar maxSatDelta = std::max(std::abs(deltaSg), std::abs(deltaSo));
260 maxSatDelta = std::max(maxSatDelta, std::abs(deltaSw));
261 maxSatDelta = std::max(maxSatDelta, std::abs(deltaSs));
262
263 // scaling factor for saturation deltas to make sure that none of them exceeds
264 // the specified threshold value.
265 Scalar satAlpha = 1.0;
266 if (maxSatDelta > bparams_.dsMax_) {
267 satAlpha = bparams_.dsMax_ / maxSatDelta;
268 }
269
270 for (unsigned pvIdx = 0; pvIdx < numEq; ++pvIdx) {
271 // calculate the update of the current primary variable. For the black-oil
272 // model we limit the pressure delta relative to the pressure's current
273 // absolute value (Default: 30%) and saturation deltas to an absolute change
274 // (Default: 20%). Further, we ensure that the R factors, solvent
275 // "saturation" and polymer concentration do not become negative after the
276 // update.
277 Scalar delta = update[pvIdx];
278
279 // limit pressure delta
280 if (pvIdx == Indices::pressureSwitchIdx) {
281 if (std::abs(delta) > bparams_.dpMaxRel_ * currentValue[pvIdx]) {
282 delta = signum(delta) * bparams_.dpMaxRel_ * currentValue[pvIdx];
283 }
284 }
285 // water saturation delta
286 else if (pvIdx == Indices::waterSwitchIdx)
287 if (currentValue.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw) {
288 delta *= satAlpha;
289 }
290 else {
291 //Ensure Rvw and Rsw factor does not become negative
292 if (delta > currentValue[ Indices::waterSwitchIdx]) {
293 delta = currentValue[ Indices::waterSwitchIdx];
294 }
295 }
296 else if (pvIdx == Indices::compositionSwitchIdx) {
297 // the switching primary variable for composition is tricky because the
298 // "reasonable" value ranges it exhibits vary widely depending on its
299 // interpretation since it can represent Sg, Rs or Rv. For now, we only
300 // limit saturation deltas and ensure that the R factors do not become
301 // negative.
302 if (currentValue.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg) {
303 delta *= satAlpha;
304 }
305 else {
306 // Ensure Rv and Rs factor does not become negative
307 if (delta > currentValue[Indices::compositionSwitchIdx]) {
308 delta = currentValue[Indices::compositionSwitchIdx];
309 }
310 }
311 }
312 else if (enableSolvent && pvIdx == Indices::solventSaturationIdx) {
313 // solvent saturation updates are also subject to the Appleyard chop
314 if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) {
315 delta *= satAlpha;
316 }
317 else {
318 // Ensure Rssolw factor does not become negative
319 if (delta > currentValue[Indices::solventSaturationIdx]) {
320 delta = currentValue[Indices::solventSaturationIdx];
321 }
322 }
323 }
324 else if (enableExtbo && pvIdx == Indices::zFractionIdx) {
325 // z fraction updates are also subject to the Appleyard chop
326 const auto& curr = currentValue[Indices::zFractionIdx]; // or currentValue[pvIdx] given the block condition
327 delta = std::clamp(delta, curr - Scalar{1.0}, curr);
328 }
329 else if (enablePolymerWeight && pvIdx == Indices::polymerMoleWeightIdx) {
330 const double sign = delta >= 0. ? 1. : -1.;
331 // maximum change of polymer molecular weight, the unit is MDa.
332 // applying this limit to stabilize the simulation. The value itself is still experimental.
333 const Scalar maxMolarWeightChange = 100.0;
334 delta = sign * std::min(std::abs(delta), maxMolarWeightChange);
335 delta *= satAlpha;
336 }
337 else if (enableFullyImplicitThermal && pvIdx == Indices::temperatureIdx) {
338 const double sign = delta >= 0. ? 1. : -1.;
339 delta = sign * std::min(std::abs(delta), bparams_.maxTempChange_);
340 }
341 else if (enableBrine && pvIdx == Indices::saltConcentrationIdx &&
342 enableSaltPrecipitation &&
343 currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp)
344 {
345 const Scalar maxSaltSaturationChange = 0.1;
346 const Scalar sign = delta >= 0. ? 1. : -1.;
347 delta = sign * std::min(std::abs(delta), maxSaltSaturationChange);
348 }
349
350 // do the actual update
351 nextValue[pvIdx] = currentValue[pvIdx] - delta;
352
353 // keep the solvent saturation between 0 and 1
354 if (enableSolvent && pvIdx == Indices::solventSaturationIdx) {
355 if (currentValue.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss) {
356 nextValue[pvIdx] = std::min(std::max(nextValue[pvIdx], Scalar{0.0}), Scalar{1.0});
357 }
358 }
359
360 // keep the z fraction between 0 and 1
361 if (enableExtbo && pvIdx == Indices::zFractionIdx) {
362 nextValue[pvIdx] = std::min(std::max(nextValue[pvIdx], Scalar{0.0}), Scalar{1.0});
363 }
364
365 // keep the polymer concentration above 0
366 if (enablePolymer && pvIdx == Indices::polymerConcentrationIdx) {
367 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
368 }
369
370 if (enablePolymerWeight && pvIdx == Indices::polymerMoleWeightIdx) {
371 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
372 const double polymerConcentration = nextValue[Indices::polymerConcentrationIdx];
373 if (polymerConcentration < 1.e-10) {
374 nextValue[pvIdx] = 0.0;
375 }
376 }
377
378 // keep the foam concentration above 0
379 if (enableFoam && pvIdx == Indices::foamConcentrationIdx) {
380 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
381 }
382
383 if (enableBrine && pvIdx == Indices::saltConcentrationIdx) {
384 // keep the salt concentration above 0
385 if (!enableSaltPrecipitation ||
386 currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Cs)
387 {
388 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
389 }
390 // keep the salt saturation below upperlimit
391 if (enableSaltPrecipitation &&
392 currentValue.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp)
393 {
394 nextValue[pvIdx] = std::min(nextValue[pvIdx], Scalar{1.0-1.e-8});
395 }
396 }
397
398 // keep the temperature within given values
399 if (enableFullyImplicitThermal && pvIdx == Indices::temperatureIdx) {
400 nextValue[pvIdx] = std::clamp(nextValue[pvIdx], bparams_.tempMin_, bparams_.tempMax_);
401 }
402
403 if (pvIdx == Indices::pressureSwitchIdx) {
404 nextValue[pvIdx] = std::clamp(nextValue[pvIdx], bparams_.pressMin_, bparams_.pressMax_);
405 }
406
407 // keep the values above 0
408 // for the biofilm and calcite, we set an upper limit equal to the initial porosity
409 // minus 1e-8. This prevents singularities (e.g., one of the calcite source term is
410 // evaluated at 1/(iniPoro - calcite)). The value 1e-8 is taken from the salt precipitation
411 // clapping above.
412 if constexpr (enableBioeffects) {
413 if (pvIdx == Indices::microbialConcentrationIdx) {
414 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
415 }
416 if (pvIdx == Indices::biofilmVolumeFractionIdx) {
417 nextValue[pvIdx] = std::clamp(nextValue[pvIdx],
418 Scalar{0.0},
419 this->problem().referencePorosity(globalDofIdx, 0) - 1e-8);
420 }
421 if constexpr (enableMICP) {
422 if (pvIdx == Indices::oxygenConcentrationIdx) {
423 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
424 }
425 if (pvIdx == Indices::ureaConcentrationIdx) {
426 nextValue[pvIdx] = std::max(nextValue[pvIdx], Scalar{0.0});
427 }
428 if (pvIdx == Indices::calciteVolumeFractionIdx) {
429 nextValue[pvIdx] = std::clamp(nextValue[pvIdx], Scalar{0.0},
430 this->problem().referencePorosity(globalDofIdx, 0) - 1e-8);
431 }
432 }
433 }
434 }
435
436 // switch the new primary variables to something which is physically meaningful.
437 // use a threshold value after a switch to make it harder to switch back
438 // immediately.
439 if (wasSwitched_[globalDofIdx]) {
440 wasSwitched_[globalDofIdx] = nextValue.adaptPrimaryVariables(this->problem(),
441 globalDofIdx,
442 bparams_.waterSaturationMax_,
443 bparams_.waterOnlyThreshold_,
444 bparams_.priVarOscilationThreshold_);
445 }
446 else {
447 wasSwitched_[globalDofIdx] = nextValue.adaptPrimaryVariables(this->problem(),
448 globalDofIdx,
449 bparams_.waterSaturationMax_,
450 bparams_.waterOnlyThreshold_);
451 }
452
453 if (wasSwitched_[globalDofIdx]) {
454 ++numPriVarsSwitched_;
455 }
456 if (bparams_.projectSaturations_) {
457 nextValue.chopAndNormalizeSaturations();
458 }
459
460 nextValue.checkDefined();
461 }
462
463private:
464 int numPriVarsSwitched_{};
465
466 BlackoilNewtonParams<Scalar> bparams_{};
467
468 // keep track of cells where the primary variable meaning has changed
469 // to detect and hinder oscillations
470 std::vector<bool> wasSwitched_{};
471};
472
473} // namespace Opm
474
475#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:120
static void registerParameters()
Register all run-time parameters for the blackoil newton method.
Definition: blackoilnewtonmethod.hpp:100
void update_(SolutionVector &nextSolution, const SolutionVector &currentSolution, const GlobalEqVector &solutionUpdate, const GlobalEqVector &currentResidual)
Definition: blackoilnewtonmethod.hpp:154
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:132
unsigned numPriVarsSwitched() const
Returns the number of degrees of freedom for which the interpretation has changed for the most recent...
Definition: blackoilnewtonmethod.hpp:110
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:205
friend ParentType
Definition: blackoilnewtonmethod.hpp:115
void update_(SolutionVector &nextSolution, const SolutionVector &currentSolution, const GlobalEqVector &solutionUpdate, const GlobalEqVector &currentResidual, const DofIndices &dofIndices)
Definition: blackoilnewtonmethod.hpp:182
The multi-dimensional Newton method.
Definition: newtonmethod.hh:100
Definition: blackoilmodel.hh:75
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.