opm-simulators
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 
48 namespace Opm::Properties {
49 
50 template <class TypeTag, class MyTypeTag>
52 
53 } // namespace Opm::Properties
54 
55 namespace Opm {
56 
62 template <class TypeTag>
63 class 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 
81 public:
82  explicit BlackOilNewtonMethod(Simulator& simulator) : ParentType(simulator)
83  {
84  bparams_.read();
85  }
86 
90  void finishInit()
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 
113 protected:
114  friend NewtonMethod<TypeTag>;
115  friend ParentType;
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 
153 public:
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 
201 protected:
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 
463 private:
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
The multi-dimensional Newton method.
The discretization specific part of he implementing the Newton algorithm.
Definition: blackoilnewtonmethod.hpp:51
The multi-dimensional Newton method.
Definition: newtonmethod.hh:64
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
constexpr int signum(Scalar val)
Template function which returns the sign of a floating point value.
Definition: signum.hh:41
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
Contains classes extending the black-oil model.
unsigned numPriVarsSwitched() const
Returns the number of degrees of freedom for which the interpretation has changed for the most recent...
Definition: blackoilnewtonmethod.hpp:110
Declares the properties required by the black oil model.
A newton solver which is specific to the black oil model.
Definition: blackoilnewtonmethod.hpp:63
static void registerParameters()
Register all run-time parameters for the blackoil newton method.
Definition: blackoilnewtonmethod.hpp:100
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
A newton solver which is specific to the black oil model.
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
Definition: blackoilmodel.hh:75
static void registerParameters()
Registers the parameters in parameter system.
Definition: blackoilnewtonmethodparams.cpp:32
void beginIteration_()
Indicates the beginning of a Newton iteration.
Definition: blackoilnewtonmethod.hpp:120