opm-simulators
NonlinearSystemBlackOilReservoir_impl.hpp
1 /*
2  Copyright 2013, 2015 SINTEF ICT, Applied Mathematics.
3  Copyright 2014, 2015 Dr. Blatt - HPC-Simulation-Software & Services
4  Copyright 2014, 2015 Statoil ASA.
5  Copyright 2015 NTNU
6  Copyright 2015, 2016, 2017 IRIS AS
7 
8  This file is part of the Open Porous Media project (OPM).
9 
10  OPM is free software: you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation, either version 3 of the License, or
13  (at your option) any later version.
14 
15  OPM is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  GNU General Public License for more details.
19 
20  You should have received a copy of the GNU General Public License
21  along with OPM. If not, see <http://www.gnu.org/licenses/>.
22 */
23 
24 #ifndef OPM_NONLINEAR_SYSTEM_BLACK_OIL_RESERVOIR_IMPL_HEADER_INCLUDED
25 #define OPM_NONLINEAR_SYSTEM_BLACK_OIL_RESERVOIR_IMPL_HEADER_INCLUDED
26 
27 #ifndef OPM_NONLINEAR_SYSTEM_BLACK_OIL_RESERVOIR_HEADER_INCLUDED
28 #include <config.h>
29 #include <opm/simulators/flow/NonlinearSystemBlackOilReservoir.hpp>
30 #endif
31 
32 #include <dune/common/timer.hh>
33 
34 #include <opm/common/ErrorMacros.hpp>
35 #include <opm/common/OpmLog/OpmLog.hpp>
36 
37 #include <opm/simulators/flow/countGlobalCells.hpp>
38 
39 #include <algorithm>
40 #include <cmath>
41 #include <filesystem>
42 #include <functional>
43 #include <iomanip>
44 #include <limits>
45 #include <memory>
46 #include <numeric>
47 #include <sstream>
48 #include <stdexcept>
49 #include <string>
50 #include <string_view>
51 #include <tuple>
52 #include <utility>
53 #include <vector>
54 
55 #include <fmt/format.h>
56 
57 namespace {
58  template <typename TypeTag>
59  std::string_view
60  make_string(const typename Opm::NonlinearSystemBlackOilReservoir<TypeTag>::DebugFlags f)
61  {
62  using F = typename Opm::NonlinearSystemBlackOilReservoir<TypeTag>::DebugFlags;
63 
64  switch (f) {
65  case F::STRICT: return "Strict";
66  case F::RELAXED: return "Relaxed";
67  case F::TUNINGDP: return "TuningDP";
68  }
69 
70  return "< ??? >";
71  }
72 } // Anonymous namespace
73 
74 namespace Opm {
75 
76 template <class TypeTag>
78 NonlinearSystemBlackOilReservoir(Simulator& simulator,
79  const ModelParameters& param,
80  BlackoilWellModel<TypeTag>& well_model,
81  const bool terminal_output)
82  : ParentType(simulator, param, well_model, terminal_output)
83  , conv_monitor_(param.monitor_params_)
84 {
85  // compute global sum of number of cells
86  global_nc_ = detail::countGlobalCells(this->grid_);
87  this->convergence_reports_.reserve(300); // Often insufficient, but avoids frequent moves.
88  // TODO: remember to fix!
89  if (this->param_.nonlinear_solver_ == "nldd") {
90  if (terminal_output) {
91  OpmLog::info("Using Non-Linear Domain Decomposition solver (nldd).");
92  }
93  nlddSolver_ = std::make_unique<NonlinearSystemNldd<TypeTag>>(*this);
94  } else if (this->param_.nonlinear_solver_ == "newton") {
95  if (terminal_output) {
96  OpmLog::info("Using Newton nonlinear solver.");
97  }
98  } else {
99  OPM_THROW(std::runtime_error, "Unknown nonlinear solver option: " +
100  this->param_.nonlinear_solver_);
101  }
102 }
103 
104 template <class TypeTag>
108 {
109  OPM_TIMEFUNCTION();
110  auto report = ParentType::prepareStep(timer);
111 
112  Dune::Timer perfTimer;
113  perfTimer.start();
114 
115  unsigned numDof = this->simulator_.model().numGridDof();
116  wasSwitched_.resize(numDof);
117  std::fill(wasSwitched_.begin(), wasSwitched_.end(), false);
118 
119  if (this->param_.update_equations_scaling_) {
120  OpmLog::error("Equation scaling not supported");
121  //updateEquationsScaling();
122  }
123 
124  if (hasNlddSolver()) {
125  nlddSolver_->prepareStep();
126  }
127 
128  report.pre_post_time += perfTimer.stop();
129 
130  auto getIdx = [](unsigned phaseIdx) -> int
131  {
132  if (FluidSystem::phaseIsActive(phaseIdx)) {
133  const unsigned sIdx = FluidSystem::solventComponentIndex(phaseIdx);
134  return FluidSystem::canonicalToActiveCompIdx(sIdx);
135  }
136 
137  return -1;
138  };
139  const auto& schedule = this->simulator_.vanguard().schedule();
140  auto& rst_conv = this->simulator_.problem().eclWriter().mutableOutputModule().getConv();
141  rst_conv.init(this->simulator_.vanguard().globalNumCells(),
142  schedule[timer.reportStepNum()].rst_config(),
143  {getIdx(FluidSystem::oilPhaseIdx),
144  getIdx(FluidSystem::gasPhaseIdx),
145  getIdx(FluidSystem::waterPhaseIdx),
146  contiPolymerEqIdx,
147  contiBrineEqIdx,
148  contiSolventEqIdx});
149 
150  return report;
151 }
152 
153 template <class TypeTag>
154 void
157  const int minIter,
158  const int maxIter,
159  const SimulatorTimerInterface& timer)
160 {
161  ParentType::initialLinearization(report,
162  minIter,
163  maxIter,
164  timer);
165 
166  // ----------- Check if converged -----------
167  std::vector<Scalar> residual_norms;
168  Dune::Timer perfTimer;
169  perfTimer.reset();
170  perfTimer.start();
171  // the step is not considered converged until at least minIter iterations is done
172  {
173  auto convrep = getConvergence(timer, maxIter, residual_norms);
174  report.converged = convrep.converged() &&
175  this->simulator_.problem().iterationContext().iteration() >= minIter;
176  ConvergenceReport::Severity severity = convrep.severityOfWorstFailure();
177  this->convergence_reports_.back().report.push_back(std::move(convrep));
178 
179  // Throw if any NaN or too large residual found.
180  if (severity == ConvergenceReport::Severity::NotANumber) {
181  this->failureReport_ += report;
182  OPM_THROW_PROBLEM(NumericalProblem, "NaN residual found!");
183  } else if (severity == ConvergenceReport::Severity::TooLarge) {
184  this->failureReport_ += report;
185  OPM_THROW_NOLOG(NumericalProblem, "Too large residual found!");
186  } else if (severity == ConvergenceReport::Severity::ConvergenceMonitorFailure) {
187  this->failureReport_ += report;
188  OPM_THROW_PROBLEM(ConvergenceMonitorFailure,
189  fmt::format(
190  "Total penalty count exceeded cut-off-limit of {}",
191  this->param_.monitor_params_.cutoff_
192  ));
193  }
194  }
195  report.update_time += perfTimer.stop();
196  this->residual_norms_history_.push_back(residual_norms);
197 }
198 
199 template <class TypeTag>
200 template <class NonlinearSolverType>
201 SimulatorReportSingle
204  NonlinearSolverType& nonlinear_solver)
205 {
206  // Model-level timestep initialization (once per timestep).
207  // markTimestepInitialized() is called later in initialLinearization(),
208  // after assembleReservoir() has triggered the well model's prepareTimeStep().
209  if (this->simulator_.problem().iterationContext().needsTimestepInit()) {
210  this->residual_norms_history_.clear();
211  this->conv_monitor_.reset();
212  this->current_relaxation_ = 1.0;
213  this->dx_old_ = 0.0;
214  this->convergence_reports_.push_back({timer.reportStepNum(), timer.currentStepNum(), {}});
215  this->convergence_reports_.back().report.reserve(11);
216  }
217 
218  SimulatorReportSingle result;
219  if (this->param_.nonlinear_solver_ != "nldd") {
220  result = this->nonlinearIterationNewton(timer, nonlinear_solver);
221  }
222  else {
223  result = this->nlddSolver_->nonlinearIterationNldd(timer, nonlinear_solver);
224  }
225 
226  auto& rst_conv = this->simulator_.problem().eclWriter().mutableOutputModule().getConv();
227  rst_conv.update(this->simulator_.model().linearizer().residual());
228 
229  this->simulator_.problem().advanceIteration();
230  return result;
231 }
232 
233 template <class TypeTag>
234 template <class NonlinearSolverType>
238  NonlinearSolverType& nonlinear_solver)
239 {
240  OPM_TIMEFUNCTION();
241 
242  SimulatorReportSingle report;
243  Dune::Timer perfTimer;
244 
245  this->initialLinearization(report,
246  this->param_.newton_min_iter_,
247  this->param_.newton_max_iter_,
248  timer);
249 
250  if (!report.converged) {
251  perfTimer.reset();
252  perfTimer.start();
253  report.total_newton_iterations = 1;
254 
255  const unsigned nc = this->simulator_.model().numGridDof();
256  BVector x(nc);
257 
258  linear_solve_setup_time_ = 0.0;
259  try {
260  this->wellModel().linearize(this->simulator().model().linearizer().jacobian(),
261  this->simulator().model().linearizer().residual());
262 
263  solveJacobianSystem(x);
264 
265  report.linear_solve_setup_time += linear_solve_setup_time_;
266  report.linear_solve_time += perfTimer.stop();
267  report.total_linear_iterations += linearIterationsLastSolve();
268  }
269  catch (...) {
270  report.linear_solve_setup_time += linear_solve_setup_time_;
271  report.linear_solve_time += perfTimer.stop();
272  report.total_linear_iterations += linearIterationsLastSolve();
273 
274  this->failureReport_ += report;
275  throw;
276  }
277 
278  perfTimer.reset();
279  perfTimer.start();
280 
281  this->wellModel().postSolve(x);
282 
283  if (this->param_.use_update_stabilization_) {
284  bool isOscillate = false;
285  bool isStagnate = false;
286  nonlinear_solver.detectOscillations(this->residual_norms_history_,
287  this->residual_norms_history_.size() - 1,
288  isOscillate,
289  isStagnate);
290  if (isOscillate) {
291  this->current_relaxation_ -= nonlinear_solver.relaxIncrement();
292  this->current_relaxation_ = std::max(this->current_relaxation_, nonlinear_solver.relaxMax());
293  if (this->terminalOutputEnabled()) {
294  OpmLog::info(" Oscillating behavior detected: Relaxation set to "
295  + std::to_string(this->current_relaxation_));
296  }
297  }
298  nonlinear_solver.stabilizeNonlinearUpdate(x, this->dx_old_, this->current_relaxation_);
299  }
300 
301  this->updateSolution(x);
302  report.update_time += perfTimer.stop();
303  }
304 
305  return report;
306 }
307 
308 template <class TypeTag>
309 typename NonlinearSystemBlackOilReservoir<TypeTag>::Scalar
310 NonlinearSystemBlackOilReservoir<TypeTag>::
311 relativeChange() const
312 {
313  Scalar resultDelta = 0.0;
314  Scalar resultDenom = 0.0;
315 
316  const auto& elemMapper = this->simulator_.model().elementMapper();
317  const auto& gridView = this->simulator_.gridView();
318  for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
319  unsigned globalElemIdx = elemMapper.index(elem);
320  const auto& priVarsNew = this->simulator_.model().solution(/*timeIdx=*/0)[globalElemIdx];
321 
322  Scalar pressureNew;
323  pressureNew = priVarsNew[Indices::pressureSwitchIdx];
324 
325  Scalar saturationsNew[FluidSystem::numPhases] = { 0.0 };
326  Scalar oilSaturationNew = 1.0;
327  if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx) &&
328  FluidSystem::numActivePhases() > 1 &&
329  priVarsNew.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw)
330  {
331  saturationsNew[FluidSystem::waterPhaseIdx] = priVarsNew[Indices::waterSwitchIdx];
332  oilSaturationNew -= saturationsNew[FluidSystem::waterPhaseIdx];
333  }
334 
335  if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) &&
336  FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) &&
337  priVarsNew.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg)
338  {
339  assert(Indices::compositionSwitchIdx != std::numeric_limits<unsigned>::max());
340  saturationsNew[FluidSystem::gasPhaseIdx] = priVarsNew[Indices::compositionSwitchIdx];
341  oilSaturationNew -= saturationsNew[FluidSystem::gasPhaseIdx];
342  }
343 
344  if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) {
345  saturationsNew[FluidSystem::oilPhaseIdx] = oilSaturationNew;
346  }
347 
348  const auto& priVarsOld = this->simulator_.model().solution(/*timeIdx=*/1)[globalElemIdx];
349 
350  Scalar pressureOld;
351  pressureOld = priVarsOld[Indices::pressureSwitchIdx];
352 
353  Scalar saturationsOld[FluidSystem::numPhases] = { 0.0 };
354  Scalar oilSaturationOld = 1.0;
355 
356  // NB fix me! adding pressures changes to saturation changes does not make sense
357  Scalar tmp = pressureNew - pressureOld;
358  resultDelta += tmp*tmp;
359  resultDenom += pressureNew*pressureNew;
360 
361  if (FluidSystem::numActivePhases() > 1) {
362  if (priVarsOld.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw) {
363  saturationsOld[FluidSystem::waterPhaseIdx] =
364  priVarsOld[Indices::waterSwitchIdx];
365  oilSaturationOld -= saturationsOld[FluidSystem::waterPhaseIdx];
366  }
367 
368  if (priVarsOld.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg)
369  {
370  assert(Indices::compositionSwitchIdx != std::numeric_limits<unsigned>::max());
371  saturationsOld[FluidSystem::gasPhaseIdx] =
372  priVarsOld[Indices::compositionSwitchIdx];
373  oilSaturationOld -= saturationsOld[FluidSystem::gasPhaseIdx];
374  }
375 
376  if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) {
377  saturationsOld[FluidSystem::oilPhaseIdx] = oilSaturationOld;
378  }
379  for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++ phaseIdx) {
380  Scalar tmpSat = saturationsNew[phaseIdx] - saturationsOld[phaseIdx];
381  resultDelta += tmpSat*tmpSat;
382  resultDenom += saturationsNew[phaseIdx]*saturationsNew[phaseIdx];
383  assert(std::isfinite(resultDelta));
384  assert(std::isfinite(resultDenom));
385  }
386  }
387  }
388 
389  resultDelta = gridView.comm().sum(resultDelta);
390  resultDenom = gridView.comm().sum(resultDenom);
391 
392  return resultDenom > 0.0 ? resultDelta / resultDenom : 0.0;
393 }
394 
395 template <class TypeTag>
396 void
399 {
400  auto& jacobian = this->simulator_.model().linearizer().jacobian().istlMatrix();
401  auto& residual = this->simulator_.model().linearizer().residual();
402  auto& linSolver = this->simulator_.model().newtonMethod().linearSolver();
403 
404  const int numSolvers = linSolver.numAvailableSolvers();
405  if (numSolvers > 1 && (linSolver.getSolveCount() % 100 == 0)) {
406  if (this->terminal_output_) {
407  OpmLog::debug("\nRunning speed test for comparing available linear solvers.");
408  }
409 
410  Dune::Timer perfTimer;
411  std::vector<double> times(numSolvers);
412  std::vector<double> setupTimes(numSolvers);
413 
414  x = 0.0;
415  std::vector<BVector> x_trial(numSolvers, x);
416  for (int solver = 0; solver < numSolvers; ++solver) {
417  linSolver.setActiveSolver(solver);
418  perfTimer.start();
419  linSolver.prepare(jacobian, residual);
420  setupTimes[solver] = perfTimer.stop();
421  perfTimer.reset();
422  linSolver.setResidual(residual);
423  perfTimer.start();
424  linSolver.solve(x_trial[solver]);
425  times[solver] = perfTimer.stop();
426  perfTimer.reset();
427  if (this->terminal_output_) {
428  OpmLog::debug(fmt::format(fmt::runtime("Solver time {}: {}"), solver, times[solver]));
429  }
430  }
431 
432  int fastest_solver = std::ranges::min_element(times) - times.begin();
433  // Use timing on rank 0 to determine fastest, must be consistent across ranks.
434  this->grid_.comm().broadcast(&fastest_solver, 1, 0);
435  this->linear_solve_setup_time_ = setupTimes[fastest_solver];
436  x = x_trial[fastest_solver];
437  linSolver.setActiveSolver(fastest_solver);
438  }
439  else {
440  x = 0.0;
441 
442  Dune::Timer perfTimer;
443  perfTimer.start();
444  linSolver.prepare(jacobian, residual);
445  this->linear_solve_setup_time_ = perfTimer.stop();
446  linSolver.setResidual(residual);
447  // actually, the error needs to be calculated after setResidual in order to
448  // account for parallelization properly. since the residual of ECFV
449  // discretizations does not need to be synchronized across processes to be
450  // consistent, this is not relevant for OPM-flow...
451  linSolver.solve(x);
452  }
453 }
454 
455 template <class TypeTag>
456 bool
459 {
460  return this->param_.tolerance_max_dp_ > 0.0 || this->param_.tolerance_max_ds_ > 0.0
461  || this->param_.tolerance_max_drs_ > 0.0 || this->param_.tolerance_max_drv_ > 0.0;
462 }
463 
464 template <class TypeTag>
465 void
468 {
469  // Init. solution update vector
470  unsigned nc = this->simulator_.model().numGridDof();
471  solUpd_.resize(nc);
472 
473  const auto& elemMapper = this->simulator_.model().elementMapper();
474  const auto& gridView = this->simulator_.gridView();
475  for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
476  // Copy solution vector to transfer primary variables meaning
477  unsigned globalElemIdx = elemMapper.index(elem);
478  solUpd_[globalElemIdx] = this->simulator_.model().solution(/*timeIdx=*/0)[globalElemIdx];
479 
480  // Ensure each element is zero
481  std::ranges::fill(solUpd_[globalElemIdx], 0.0);
482  }
483 }
484 
485 template <class TypeTag>
486 void
487 NonlinearSystemBlackOilReservoir<TypeTag>::
488 storeSolutionUpdate(const GlobalEqVector& dx)
489 {
490  const auto& elemMapper = this->simulator_.model().elementMapper();
491  const auto& gridView = this->simulator_.gridView();
492  for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
493  // Get cell vectors
494  unsigned globalElemIdx = elemMapper.index(elem);
495  auto& value = solUpd_[globalElemIdx];
496  const auto& update = dx[globalElemIdx];
497  assert(value.size() == update.size());
498 
499  // Transfer update from dx to solution update container (SolutionVector type)
500  std::ranges::copy(update, value.begin());
501  }
502 }
503 
504 template <class TypeTag>
505 typename NonlinearSystemBlackOilReservoir<TypeTag>::MaxSolutionUpdateData
506 NonlinearSystemBlackOilReservoir<TypeTag>::
507 getMaxSolutionUpdate(const std::vector<unsigned>& ixCells)
508 {
509  static constexpr bool enableSolvent =
510  Indices::solventSaturationIdx != std::numeric_limits<unsigned>::max();
511  static constexpr bool enableBrine =
512  Indices::saltConcentrationIdx != std::numeric_limits<unsigned>::max();
513 
514  // Init output
515  Scalar dPMax = 0.0;
516  Scalar dSMax = 0.0;
517  Scalar dRsMax = 0.0;
518  Scalar dRvMax = 0.0;
519 
520  // Loop over solution update, get the correct variables and calculate max.
521  for (const auto& ix : ixCells) {
522  const auto& value = solUpd_[ix];
523  for (unsigned pvIdx = 0; pvIdx < value.size(); ++pvIdx) {
524  if (pvIdx == Indices::pressureSwitchIdx) {
525  dPMax = std::max(dPMax, std::abs(value[pvIdx]));
526  }
527  else if ((pvIdx == Indices::waterSwitchIdx
528  && value.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw)
529  || (pvIdx == Indices::compositionSwitchIdx
530  && value.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg)
531  || (enableSolvent && pvIdx == Indices::solventSaturationIdx
532  && value.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss)
533  || (enableBrine && enableSaltPrecipitation && pvIdx == Indices::saltConcentrationIdx
534  && value.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp) ) {
535  dSMax = std::max(dSMax, std::abs(value[pvIdx]));
536  }
537  else if (pvIdx == Indices::compositionSwitchIdx
538  && value.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rs) {
539  dRsMax = std::max(dRsMax, std::abs(value[pvIdx]));
540  }
541  else if (pvIdx == Indices::compositionSwitchIdx
542  && value.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rv) {
543  dRvMax = std::max(dRvMax, std::abs(value[pvIdx]));
544  }
545  }
546  }
547 
548  // Communicate max values
549  dPMax = this->grid_.comm().max(dPMax);
550  dSMax = this->grid_.comm().max(dSMax);
551  dRsMax = this->grid_.comm().max(dRsMax);
552  dRvMax = this->grid_.comm().max(dRvMax);
553 
554  return { dPMax, dSMax, dRsMax, dRvMax };
555 }
556 
557 template <class TypeTag>
558 std::tuple<typename NonlinearSystemBlackOilReservoir<TypeTag>::Scalar,
559  typename NonlinearSystemBlackOilReservoir<TypeTag>::Scalar>
560 NonlinearSystemBlackOilReservoir<TypeTag>::
561 convergenceReduction(Parallel::Communication comm,
562  const Scalar pvSumLocal,
563  const Scalar numAquiferPvSumLocal,
564  std::vector< Scalar >& R_sum,
565  std::vector< Scalar >& maxCoeff,
566  std::vector< Scalar >& B_avg)
567 {
568  return ParentType::convergenceReduction(comm,
569  pvSumLocal,
570  numAquiferPvSumLocal,
571  R_sum,
572  maxCoeff,
573  B_avg);
574 }
575 
576 template <class TypeTag>
577 std::pair<typename NonlinearSystemBlackOilReservoir<TypeTag>::Scalar,
578  typename NonlinearSystemBlackOilReservoir<TypeTag>::Scalar>
580 localConvergenceData(std::vector<Scalar>& R_sum,
581  std::vector<Scalar>& maxCoeff,
582  std::vector<Scalar>& B_avg,
583  std::vector<int>& maxCoeffCell)
584 {
585  OPM_TIMEBLOCK(localConvergenceData);
586  Scalar pvSumLocal = 0.0;
587  Scalar numAquiferPvSumLocal = 0.0;
588  const auto& model = this->simulator_.model();
589  const auto& problem = this->simulator_.problem();
590 
591  const auto& residual = this->simulator_.model().linearizer().residual();
592 
593  ElementContext elemCtx(this->simulator_);
594  const auto& gridView = this->simulator().gridView();
595  IsNumericalAquiferCell isNumericalAquiferCell(gridView.grid());
596  OPM_BEGIN_PARALLEL_TRY_CATCH();
597  for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
598  elemCtx.updatePrimaryStencil(elem);
599  elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
600 
601  const unsigned cell_idx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
602  const auto& intQuants = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0);
603  const auto& fs = intQuants.fluidState();
604 
605  const auto pvValue = problem.referencePorosity(cell_idx, /*timeIdx=*/0) *
606  model.dofTotalVolume(cell_idx);
607  pvSumLocal += pvValue;
608 
609  if (isNumericalAquiferCell(elem)) {
610  numAquiferPvSumLocal += pvValue;
611  }
612 
613  this->getMaxCoeff(cell_idx, intQuants, fs, residual, pvValue,
614  B_avg, R_sum, maxCoeff, maxCoeffCell);
615  }
616 
617  OPM_END_PARALLEL_TRY_CATCH("NonlinearSystemBlackOilReservoir::localConvergenceData() failed: ", this->grid_.comm());
618 
619  // compute local average in terms of global number of elements
620  const int bSize = B_avg.size();
621  for (int i = 0; i < bSize; ++i) {
622  B_avg[i] /= Scalar(this->global_nc_);
623  }
624 
625  return {pvSumLocal, numAquiferPvSumLocal};
626 }
627 
628 template <class TypeTag>
631 characteriseCnvPvSplit(const std::vector<Scalar>& B_avg, const double dt)
632 {
633  OPM_TIMEBLOCK(computeCnvErrorPv);
634 
635  // 0: cnv <= tolerance_cnv
636  // 1: tolerance_cnv < cnv <= tolerance_cnv_relaxed
637  // 2: tolerance_cnv_relaxed < cnv
638  constexpr auto numPvGroups = std::vector<double>::size_type{3};
639 
640  auto cnvPvSplit = std::pair<std::vector<double>, std::vector<int>> {
641  std::piecewise_construct,
642  std::forward_as_tuple(numPvGroups),
643  std::forward_as_tuple(numPvGroups)
644  };
645 
646  auto maxCNV = [&B_avg, dt](const auto& residual, const double pvol)
647  {
648  return (dt / pvol) *
649  std::inner_product(residual.begin(), residual.end(),
650  B_avg.begin(), Scalar{0},
651  [](const Scalar m, const auto& x)
652  {
653  using std::abs;
654  return std::max(m, abs(x));
655  }, std::multiplies<>{});
656  };
657 
658  auto& [splitPV, cellCntPV] = cnvPvSplit;
659 
660  const auto& model = this->simulator().model();
661  const auto& problem = this->simulator().problem();
662  const auto& residual = model.linearizer().residual();
663  const auto& gridView = this->simulator().gridView();
664 
665  const IsNumericalAquiferCell isNumericalAquiferCell(gridView.grid());
666 
667  ElementContext elemCtx(this->simulator());
668 
669  std::vector<unsigned> ixCells;
670 
671  OPM_BEGIN_PARALLEL_TRY_CATCH();
672  for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
673  // Skip cells of numerical Aquifer
674  if (isNumericalAquiferCell(elem)) {
675  continue;
676  }
677 
678  elemCtx.updatePrimaryStencil(elem);
679 
680  const unsigned cell_idx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
681  const auto pvValue = problem.referencePorosity(cell_idx, /*timeIdx=*/0)
682  * model.dofTotalVolume(cell_idx);
683 
684  const auto maxCnv = maxCNV(residual[cell_idx], pvValue);
685 
686  const auto ix = (maxCnv > this->param_.tolerance_cnv_)
687  + (maxCnv > this->param_.tolerance_cnv_relaxed_);
688 
689  splitPV[ix] += static_cast<double>(pvValue);
690  ++cellCntPV[ix];
691 
692  // For dP and dS check, we need cell indices of [1] violations
693  if ( ix > 0 &&
694  (this->param_.tolerance_max_dp_ > 0.0 || this->param_.tolerance_max_ds_ > 0.0
695  || this->param_.tolerance_max_drs_ > 0.0 || this->param_.tolerance_max_drv_ > 0.0 ) ) {
696  ixCells.push_back(cell_idx);
697  }
698  }
699 
700  OPM_END_PARALLEL_TRY_CATCH("NonlinearSystemBlackOilReservoir::characteriseCnvPvSplit() failed: ",
701  this->grid_.comm());
702 
703  this->grid_.comm().sum(splitPV .data(), splitPV .size());
704  this->grid_.comm().sum(cellCntPV.data(), cellCntPV.size());
705 
706  return { cnvPvSplit, ixCells };
707 }
708 
709 template <class TypeTag>
712 getReservoirConvergence(const double reportTime,
713  const double dt,
714  const int maxIter,
715  std::vector<Scalar>& B_avg,
716  std::vector<Scalar>& residual_norms)
717 {
718  OPM_TIMEBLOCK(getReservoirConvergence);
719  using Vector = std::vector<Scalar>;
720 
721  const auto& iterCtx = this->simulator_.problem().iterationContext();
722 
723  ConvergenceReport report{reportTime};
724 
725  const int numComp = numEq;
726 
727  Vector R_sum(numComp, Scalar{0});
728  Vector maxCoeff(numComp, std::numeric_limits<Scalar>::lowest());
729  std::vector<int> maxCoeffCell(numComp, -1);
730 
731  const auto [pvSumLocal, numAquiferPvSumLocal] =
732  this->localConvergenceData(R_sum, maxCoeff, B_avg, maxCoeffCell);
733 
734  // compute global sum and max of quantities
735  const auto& [pvSum, numAquiferPvSum] =
736  this->convergenceReduction(this->grid_.comm(),
737  pvSumLocal,
738  numAquiferPvSumLocal,
739  R_sum, maxCoeff, B_avg);
740 
741  auto cnvSplitData = this->characteriseCnvPvSplit(B_avg, dt);
742  report.setCnvPoreVolSplit(cnvSplitData.cnvPvSplit,
743  pvSum - numAquiferPvSum);
744 
745  // For each iteration, we need to determine whether to use the
746  // relaxed tolerances. To disable the usage of relaxed
747  // tolerances, you can set the relaxed tolerances as the strict
748  // tolerances. If min_strict_mb_iter = -1 (default) we use a
749  // relaxed tolerance for the mass balance for the last
750  // iterations. For positive values we use the relaxed tolerance
751  // after the given number of iterations
752  const bool relax_final_iteration_mb =
753  this->param_.min_strict_mb_iter_ < 0 && iterCtx.iteration() == maxIter;
754 
755  const bool relax_iter_mb = this->param_.min_strict_mb_iter_ >= 0 &&
756  iterCtx.shouldRelax(this->param_.min_strict_mb_iter_);
757 
758  const bool use_relaxed_mb = relax_final_iteration_mb
759  || relax_iter_mb;
760 
761  // If min_strict_cnv_iter = -1 we use a relaxed tolerance for
762  // the cnv for the last iterations. For positive values we use
763  // the relaxed tolerance after the given number of iterations.
764  // We also use relaxed tolerances for cells with total
765  // pore-volume less than relaxed_max_pv_fraction_. Default
766  // value of relaxed_max_pv_fraction_ is 0.03
767  const bool relax_final_iteration_cnv =
768  this->param_.min_strict_cnv_iter_ < 0 && iterCtx.iteration() == maxIter;
769 
770  const bool relax_iter_cnv = this->param_.min_strict_cnv_iter_ >= 0 &&
771  iterCtx.shouldRelax(this->param_.min_strict_cnv_iter_);
772 
773  // Note trailing parentheses here, just before the final
774  // semicolon. This is an immediately invoked function
775  // expression which calculates a single boolean value.
776  const auto relax_pv_fraction_cnv =
777  [&report, this, eligible = pvSum - numAquiferPvSum]()
778  {
779  const auto& cnvPvSplit = report.cnvPvSplit().first;
780 
781  // [1]: tol < cnv <= relaxed
782  // [2]: relaxed < cnv
783  Scalar cnvPvSum = static_cast<Scalar>(cnvPvSplit[1] + cnvPvSplit[2]);
784  return cnvPvSum < this->param_.relaxed_max_pv_fraction_ * eligible &&
785  cnvPvSum > 0.0;
786  }();
787 
788  // If tolerances for solution changes are met, we use the
789  // relaxed cnv tolerance. Note that all tolerances > 0.0
790  // must be met to enable use of relaxed cnv tolerance.
791  MaxSolutionUpdateData maxSolUpd;
792  const bool use_dp_tol = this->param_.tolerance_max_dp_ > 0.0;
793  const bool use_ds_tol = this->param_.tolerance_max_ds_ > 0.0;
794  const bool use_drs_tol = this->param_.tolerance_max_drs_ > 0.0;
795  const bool use_drv_tol = this->param_.tolerance_max_drv_ > 0.0;
796  const bool use_dsol_tol = use_dp_tol || use_ds_tol || use_drs_tol || use_drv_tol;
797  bool relax_dsol_cnv = false;
798  if (!iterCtx.isFirstGlobalIteration() && use_dsol_tol) {
799  maxSolUpd = getMaxSolutionUpdate(cnvSplitData.ixCells);
800  relax_dsol_cnv =
801  (!use_dp_tol || (maxSolUpd.dPMax > 0.0 && maxSolUpd.dPMax < this->param_.tolerance_max_dp_)) &&
802  (!use_ds_tol || (maxSolUpd.dSMax > 0.0 && maxSolUpd.dSMax < this->param_.tolerance_max_ds_)) &&
803  (!use_drs_tol || (maxSolUpd.dRsMax > 0.0 && maxSolUpd.dRsMax < this->param_.tolerance_max_drs_)) &&
804  (!use_drv_tol || (maxSolUpd.dRvMax > 0.0 && maxSolUpd.dRvMax < this->param_.tolerance_max_drv_));
805  }
806 
807  // Determine if relaxed CNV tolerances should be used
808  const bool use_relaxed_cnv = relax_final_iteration_cnv
809  || relax_pv_fraction_cnv
810  || relax_iter_cnv
811  || relax_dsol_cnv;
812 
813  // Ensure that CNV convergence criteria is met when max.
814  // solution change tolerances have been fulfilled
815  Scalar tolerance_cnv_relaxed = relax_dsol_cnv ? 1e20 : this->param_.tolerance_cnv_relaxed_;
816 
817  const auto tol_cnv = use_relaxed_cnv ? tolerance_cnv_relaxed : this->param_.tolerance_cnv_;
818  const auto tol_mb = use_relaxed_mb ? this->param_.tolerance_mb_relaxed_ : this->param_.tolerance_mb_;
819  const auto tol_cnv_energy = use_relaxed_cnv ? this->param_.tolerance_cnv_energy_relaxed_ : this->param_.tolerance_cnv_energy_;
820  const auto tol_eb = use_relaxed_mb ? this->param_.tolerance_energy_balance_relaxed_ : this->param_.tolerance_energy_balance_;
821 
822  // Finish computation
823  std::vector<Scalar> CNV(numComp);
824  std::vector<Scalar> mass_balance_residual(numComp);
825  for (int compIdx = 0; compIdx < numComp; ++compIdx)
826  {
827  CNV[compIdx] = B_avg[compIdx] * dt * maxCoeff[compIdx];
828  mass_balance_residual[compIdx] = std::abs(B_avg[compIdx]*R_sum[compIdx]) * dt / pvSum;
829  residual_norms.push_back(CNV[compIdx]);
830  }
831 
832  using CR = ConvergenceReport;
833  for (int compIdx = 0; compIdx < numComp; ++compIdx) {
834  const Scalar res[2] = {
835  mass_balance_residual[compIdx], CNV[compIdx],
836  };
837 
838  const CR::ReservoirFailure::Type types[2] = {
839  CR::ReservoirFailure::Type::MassBalance,
840  CR::ReservoirFailure::Type::Cnv,
841  };
842 
843  Scalar tol[2] = { tol_mb, tol_cnv, };
844  if (has_energy_ && compIdx == contiEnergyEqIdx) {
845  tol[0] = tol_eb;
846  tol[1] = tol_cnv_energy;
847  }
848 
849  this->addReservoirConvergenceMetrics(
850  report,
851  compIdx,
852  this->compNames_.name(compIdx),
853  std::span<const Scalar>{res},
854  std::span<const CR::ReservoirFailure::Type>{types},
855  std::span<const Scalar>{tol},
856  maxResidualAllowed(),
857  [this](const std::string& message)
858  {
859  if (this->terminal_output_) {
860  OpmLog::debug(message);
861  }
862  });
863  }
864 
865  // Compute the Newton convergence per cell.
866  this->convergencePerCell(B_avg, dt, tol_cnv, tol_cnv_energy);
867 
868  // Output of residuals.
869  if (this->terminal_output_) {
870  // Only rank 0 does print to std::cout
871  if (iterCtx.isFirstGlobalIteration()) {
872  std::string msg = "Iter";
873  for (int compIdx = 0; compIdx < numComp; ++compIdx) {
874  msg += " MB(";
875  msg += this->compNames_.name(compIdx)[0];
876  msg += ") ";
877  }
878 
879  for (int compIdx = 0; compIdx < numComp; ++compIdx) {
880  msg += " CNV(";
881  msg += this->compNames_.name(compIdx)[0];
882  msg += ") ";
883  }
884 
885  if (use_dsol_tol) {
886  msg += use_dp_tol ? " DP " : "";
887  msg += use_ds_tol ? " DS " : "";
888  msg += use_drs_tol ? " DRS " : "";
889  msg += use_drv_tol ? " DRV " : "";
890  }
891 
892  msg += " MBFLAG";
893  msg += " CNVFLAG";
894 
895  OpmLog::debug(msg);
896  }
897 
898  std::ostringstream ss;
899  const std::streamsize oprec = ss.precision(3);
900  const std::ios::fmtflags oflags = ss.setf(std::ios::scientific);
901 
902  ss << std::setw(4) << iterCtx.iteration();
903  for (int compIdx = 0; compIdx < numComp; ++compIdx) {
904  ss << std::setw(11) << mass_balance_residual[compIdx];
905  }
906 
907  for (int compIdx = 0; compIdx < numComp; ++compIdx) {
908  ss << std::setw(11) << CNV[compIdx];
909  }
910 
911  if (use_dsol_tol) {
912  auto print_dsol =
913  [&] (bool use_tol, Scalar dsol) {
914  if (!use_tol) {
915  return;
916  }
917  if (iterCtx.isFirstGlobalIteration() || dsol <= 0.0) {
918  ss << std::string(5, ' ') << "-" << std::string(5, ' ');
919  }
920  else {
921  ss << std::setw(11) << dsol;
922  }
923  };
924  print_dsol(use_dp_tol, maxSolUpd.dPMax);
925  print_dsol(use_ds_tol, maxSolUpd.dSMax);
926  print_dsol(use_drs_tol, maxSolUpd.dRsMax);
927  print_dsol(use_drv_tol, maxSolUpd.dRvMax);
928  }
929 
930  const auto mb_flag = use_relaxed_mb
931  ? DebugFlags::RELAXED
932  : DebugFlags::STRICT;
933 
934  const auto cnv_flag = relax_dsol_cnv ?
935  DebugFlags::TUNINGDP
936  : (use_relaxed_cnv
937  ? DebugFlags::RELAXED
938  : DebugFlags::STRICT);
939 
940  ss << std::setw(9) << make_string<TypeTag>(mb_flag)
941  << std::setw(9) << make_string<TypeTag>(cnv_flag);
942 
943  ss.precision(oprec);
944  ss.flags(oflags);
945 
946  OpmLog::debug(ss.str());
947  }
948 
949  return report;
950 }
951 
952 template <class TypeTag>
953 void
955 convergencePerCell(const std::vector<Scalar>& B_avg,
956  const double dt,
957  const double tol_cnv,
958  const double tol_cnv_energy)
959 {
960  auto& rst_conv = this->simulator_.problem().eclWriter().mutableOutputModule().getConv();
961  if (!rst_conv.hasConv()) {
962  return;
963  }
964 
965  if (this->simulator_.problem().iterationContext().isFirstGlobalIteration()) {
966  rst_conv.prepareConv();
967  }
968 
969  const auto& residual = this->simulator_.model().linearizer().residual();
970  const auto& gridView = this->simulator_.gridView();
971  const IsNumericalAquiferCell isNumericalAquiferCell(gridView.grid());
972  ElementContext elemCtx(this->simulator());
973  std::vector<int> convNewt(residual.size(), 0);
974  OPM_BEGIN_PARALLEL_TRY_CATCH();
975  unsigned idx = 0;
976  const int numComp = B_avg.size();
977  for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
978  elemCtx.updatePrimaryStencil(elem);
979 
980  const unsigned cell_idx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
981  const auto pvValue = this->simulator_.problem().referencePorosity(cell_idx, /*timeIdx=*/0) *
982  this->simulator_.model().dofTotalVolume(cell_idx);
983  for (int compIdx = 0; compIdx < numComp; ++compIdx) {
984  const auto tol = (has_energy_ && compIdx == contiEnergyEqIdx) ? tol_cnv_energy : tol_cnv;
985  const Scalar cnv = std::abs(B_avg[compIdx] * residual[cell_idx][compIdx]) * dt / pvValue;
986  if (std::isnan(cnv) || cnv > maxResidualAllowed() || cnv < 0.0 || cnv > tol) {
987  convNewt[idx] = 1;
988  break;
989  }
990  }
991  ++idx;
992  }
993  OPM_END_PARALLEL_TRY_CATCH("NonlinearSystemBlackOilReservoir::convergencePerCell() failed: ",
994  this->grid_.comm());
995  rst_conv.updateNewton(convNewt);
996 }
997 
998 template <class TypeTag>
1002  const int maxIter,
1003  std::vector<Scalar>& residual_norms)
1004 {
1005  OPM_TIMEBLOCK(getConvergence);
1006  // Get convergence reports for reservoir and wells.
1007  std::vector<Scalar> B_avg(numEq, 0.0);
1008  auto report = getReservoirConvergence(timer.simulationTimeElapsed(),
1009  timer.currentStepLength(),
1010  maxIter, B_avg, residual_norms);
1011  {
1012  OPM_TIMEBLOCK(getWellConvergence);
1013  report += this->wellModel().getWellConvergence(B_avg,
1014  /*checkWellGroupControlsAndNetwork*/report.converged());
1015  }
1016 
1017  conv_monitor_.checkPenaltyCard(report, this->simulator_.problem().iterationContext().iteration());
1018 
1019  return report;
1020 }
1021 
1022 template <class TypeTag>
1023 std::vector<std::vector<typename NonlinearSystemBlackOilReservoir<TypeTag>::Scalar> >
1025 computeFluidInPlace(const std::vector<int>& /*fipnum*/) const
1026 {
1027  OPM_TIMEBLOCK(computeFluidInPlace);
1028  // assert(true)
1029  // return an empty vector
1030  std::vector<std::vector<Scalar> > regionValues(0, std::vector<Scalar>(0,0.0));
1031  return regionValues;
1032 }
1033 
1034 template <class TypeTag>
1035 const SimulatorReport&
1038 {
1039  if (!hasNlddSolver()) {
1040  OPM_THROW(std::runtime_error, "Cannot get local reports from a model without NLDD solver");
1041  }
1042  return nlddSolver_->localAccumulatedReports();
1043 }
1044 
1045 template <class TypeTag>
1046 const std::vector<SimulatorReport>&
1049 {
1050  if (!nlddSolver_)
1051  OPM_THROW(std::runtime_error, "Cannot get domain reports from a model without NLDD solver");
1052  return nlddSolver_->domainAccumulatedReports();
1053 }
1054 
1055 template <class TypeTag>
1056 void
1058 writeNonlinearIterationsPerCell(const std::filesystem::path& odir) const
1059 {
1060  if (hasNlddSolver()) {
1061  nlddSolver_->writeNonlinearIterationsPerCell(odir);
1062  }
1063 }
1064 
1065 template <class TypeTag>
1066 void
1068 writePartitions(const std::filesystem::path& odir) const
1069 {
1070  if (hasNlddSolver()) {
1071  nlddSolver_->writePartitions(odir);
1072  return;
1073  }
1074 
1075  const auto& elementMapper = this->simulator().model().elementMapper();
1076  const auto& cartMapper = this->simulator().vanguard().cartesianIndexMapper();
1077 
1078  const auto& grid = this->simulator().vanguard().grid();
1079  const auto& comm = grid.comm();
1080  const auto nDigit = 1 + static_cast<int>(std::floor(std::log10(comm.size())));
1081 
1082  std::ofstream pfile {odir / fmt::format("{1:0>{0}}", nDigit, comm.rank())};
1083 
1084  for (const auto& cell : elements(grid.leafGridView(), Dune::Partitions::interior)) {
1085  pfile << comm.rank() << ' '
1086  << cartMapper.cartesianIndex(elementMapper.index(cell)) << ' '
1087  << comm.rank() << '\n';
1088  }
1089 }
1090 
1091 template <class TypeTag>
1092 template<class FluidState, class Residual>
1093 void
1094 NonlinearSystemBlackOilReservoir<TypeTag>::
1095 getMaxCoeff(const unsigned cell_idx,
1096  const IntensiveQuantities& intQuants,
1097  const FluidState& fs,
1098  const Residual& modelResid,
1099  const Scalar pvValue,
1100  std::vector<Scalar>& B_avg,
1101  std::vector<Scalar>& R_sum,
1102  std::vector<Scalar>& maxCoeff,
1103  std::vector<int>& maxCoeffCell)
1104 {
1105  for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx)
1106  {
1107  if (!FluidSystem::phaseIsActive(phaseIdx)) {
1108  continue;
1109  }
1110 
1111  const unsigned sIdx = FluidSystem::solventComponentIndex(phaseIdx);
1112  const unsigned compIdx = FluidSystem::canonicalToActiveCompIdx(sIdx);
1113 
1114  B_avg[compIdx] += 1.0 / fs.invB(phaseIdx).value();
1115  const auto R2 = modelResid[cell_idx][compIdx];
1116 
1117  R_sum[compIdx] += R2;
1118  const Scalar Rval = std::abs(R2) / pvValue;
1119  if (Rval > maxCoeff[compIdx]) {
1120  maxCoeff[compIdx] = Rval;
1121  maxCoeffCell[compIdx] = cell_idx;
1122  }
1123  }
1124 
1125  if constexpr (has_solvent_) {
1126  B_avg[contiSolventEqIdx] +=
1127  1.0 / intQuants.solventInverseFormationVolumeFactor().value();
1128  const auto R2 = modelResid[cell_idx][contiSolventEqIdx];
1129  R_sum[contiSolventEqIdx] += R2;
1130  maxCoeff[contiSolventEqIdx] = std::max(maxCoeff[contiSolventEqIdx],
1131  std::abs(R2) / pvValue);
1132  }
1133  if constexpr (has_extbo_) {
1134  B_avg[contiZfracEqIdx] += 1.0 / fs.invB(FluidSystem::gasPhaseIdx).value();
1135  const auto R2 = modelResid[cell_idx][contiZfracEqIdx];
1136  R_sum[ contiZfracEqIdx ] += R2;
1137  maxCoeff[contiZfracEqIdx] = std::max(maxCoeff[contiZfracEqIdx],
1138  std::abs(R2) / pvValue);
1139  }
1140  if constexpr (has_polymer_) {
1141  B_avg[contiPolymerEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1142  const auto R2 = modelResid[cell_idx][contiPolymerEqIdx];
1143  R_sum[contiPolymerEqIdx] += R2;
1144  maxCoeff[contiPolymerEqIdx] = std::max(maxCoeff[contiPolymerEqIdx],
1145  std::abs(R2) / pvValue);
1146  }
1147  if constexpr (has_foam_) {
1148  B_avg[ contiFoamEqIdx ] += 1.0 / fs.invB(FluidSystem::gasPhaseIdx).value();
1149  const auto R2 = modelResid[cell_idx][contiFoamEqIdx];
1150  R_sum[contiFoamEqIdx] += R2;
1151  maxCoeff[contiFoamEqIdx] = std::max(maxCoeff[contiFoamEqIdx],
1152  std::abs(R2) / pvValue);
1153  }
1154  if constexpr (has_brine_) {
1155  B_avg[ contiBrineEqIdx ] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1156  const auto R2 = modelResid[cell_idx][contiBrineEqIdx];
1157  R_sum[contiBrineEqIdx] += R2;
1158  maxCoeff[contiBrineEqIdx] = std::max(maxCoeff[contiBrineEqIdx],
1159  std::abs(R2) / pvValue);
1160  }
1161 
1162  if constexpr (has_polymermw_) {
1163  static_assert(has_polymer_);
1164 
1165  B_avg[contiPolymerMWEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1166  // the residual of the polymer molecular equation is scaled down by a 100, since molecular weight
1167  // can be much bigger than 1, and this equation shares the same tolerance with other mass balance equations
1168  // TODO: there should be a more general way to determine the scaling-down coefficient
1169  const auto R2 = modelResid[cell_idx][contiPolymerMWEqIdx] / 100.;
1170  R_sum[contiPolymerMWEqIdx] += R2;
1171  maxCoeff[contiPolymerMWEqIdx] = std::max(maxCoeff[contiPolymerMWEqIdx],
1172  std::abs(R2) / pvValue);
1173  }
1174 
1175  if constexpr (has_energy_) {
1176  B_avg[contiEnergyEqIdx] += 1.0;
1177  const auto R2 = modelResid[cell_idx][contiEnergyEqIdx];
1178  R_sum[contiEnergyEqIdx] += R2;
1179  maxCoeff[contiEnergyEqIdx] = std::max(maxCoeff[contiEnergyEqIdx],
1180  std::abs(R2) / pvValue);
1181  }
1182 
1183  if constexpr (has_bioeffects_) {
1184  B_avg[contiMicrobialEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1185  const auto R1 = modelResid[cell_idx][contiMicrobialEqIdx];
1186  R_sum[contiMicrobialEqIdx] += R1;
1187  maxCoeff[contiMicrobialEqIdx] = std::max(maxCoeff[contiMicrobialEqIdx],
1188  std::abs(R1) / pvValue);
1189  B_avg[contiBiofilmEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1190  const auto R2 = modelResid[cell_idx][contiBiofilmEqIdx];
1191  R_sum[contiBiofilmEqIdx] += R2;
1192  maxCoeff[contiBiofilmEqIdx] = std::max(maxCoeff[contiBiofilmEqIdx],
1193  std::abs(R2) / pvValue);
1194  if constexpr (has_micp_) {
1195  B_avg[contiOxygenEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1196  const auto R3 = modelResid[cell_idx][contiOxygenEqIdx];
1197  R_sum[contiOxygenEqIdx] += R3;
1198  maxCoeff[contiOxygenEqIdx] = std::max(maxCoeff[contiOxygenEqIdx],
1199  std::abs(R3) / pvValue);
1200  B_avg[contiUreaEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1201  const auto R4 = modelResid[cell_idx][contiUreaEqIdx];
1202  R_sum[contiUreaEqIdx] += R4;
1203  maxCoeff[contiUreaEqIdx] = std::max(maxCoeff[contiUreaEqIdx],
1204  std::abs(R4) / pvValue);
1205  B_avg[contiCalciteEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1206  const auto R5 = modelResid[cell_idx][contiCalciteEqIdx];
1207  R_sum[contiCalciteEqIdx] += R5;
1208  maxCoeff[contiCalciteEqIdx] = std::max(maxCoeff[contiCalciteEqIdx],
1209  std::abs(R5) / pvValue);
1210  }
1211  }
1212 }
1213 
1214 } // namespace Opm
1215 
1216 #endif // OPM_NONLINEAR_SYSTEM_BLACK_OIL_RESERVOIR_IMPL_HEADER_INCLUDED
Definition: NonlinearSystemBlackOilReservoir.hpp:118
Definition: AquiferGridUtils.hpp:35
Class for handling the blackoil well model.
Definition: BlackoilModelProperties.hpp:32
std::unique_ptr< NonlinearSystemNldd< TypeTag > > nlddSolver_
Non-linear DD solver.
Definition: NonlinearSystemBlackOilReservoir.hpp:306
virtual int reportStepNum() const
Current report step number. This might differ from currentStepNum in case of sub stepping.
Definition: SimulatorTimerInterface.hpp:109
std::pair< Scalar, Scalar > localConvergenceData(std::vector< Scalar > &R_sum, std::vector< Scalar > &maxCoeff, std::vector< Scalar > &B_avg, std::vector< int > &maxCoeffCell)
Get reservoir quantities on this process needed for convergence calculations.
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:580
void writeNonlinearIterationsPerCell(const std::filesystem::path &odir) const
Write the number of nonlinear iterations per cell to a file in ResInsight compatible format...
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:1058
const SimulatorReport & localAccumulatedReports() const
return the statistics of local solves accumulated for this rank
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:1037
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
CnvPvSplitData characteriseCnvPvSplit(const std::vector< Scalar > &B_avg, const double dt)
Compute pore-volume/cell count split among "converged", "relaxed converged", "unconverged" cells base...
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:631
Solver parameters for the NonlinearSystemBlackOilReservoir.
Definition: BlackoilModelParameters.hpp:200
virtual double currentStepLength() const =0
Current step length.
bool shouldStoreSolutionUpdate() const override
Get solution update vector as a PrimaryVariable.
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:458
ConvergenceReport getConvergence(const SimulatorTimerInterface &timer, const int maxIter, std::vector< Scalar > &residual_norms)
Compute convergence based on total mass balance (tol_mb) and maximum residual mass balance (tol_cnv)...
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:1001
void solveJacobianSystem(BVector &x)
Solve the Jacobian system Jx = r where J is the Jacobian and r is the residual.
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:398
A model implementation for three-phase black oil.
Definition: NonlinearSystemBlackOilReservoir.hpp:62
SimulatorReportSingle prepareStep(const SimulatorTimerInterface &timer)
Called once before each time step.
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:107
const std::vector< SimulatorReport > & domainAccumulatedReports() const
return the statistics of local solves accumulated for each domain on this rank
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:1048
A struct for returning timing data from a simulator to its caller.
Definition: SimulatorReport.hpp:33
Interface class for SimulatorTimer objects, to be improved.
Definition: SimulatorTimerInterface.hpp:33
void convergencePerCell(const std::vector< Scalar > &B_avg, const double dt, const double tol_cnv, const double tol_cnv_energy)
Compute the number of Newtons required by each cell in order to satisfy the solution change convergen...
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:955
virtual int currentStepNum() const =0
Current step number.
long int global_nc_
The number of cells of the global grid.
Definition: NonlinearSystemBlackOilReservoir.hpp:302
NonlinearSystemBlackOilReservoir(Simulator &simulator, const ModelParameters &param, BlackoilWellModel< TypeTag > &well_model, const bool terminal_output)
Construct the model.
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:78
Represents the convergence status of the whole simulator, to make it possible to query and store the ...
Definition: ConvergenceReport.hpp:37
Definition: SimulatorReport.hpp:121
Definition: NonlinearSystem.hpp:43
SimulatorReportSingle nonlinearIteration(const SimulatorTimerInterface &timer, NonlinearSolverType &nonlinear_solver)
Called once per nonlinear iteration.
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:203
std::vector< std::vector< Scalar > > computeFluidInPlace(const T &, const std::vector< int > &fipnum) const
Wrapper required due to not following generic API.
Definition: NonlinearSystemBlackOilReservoir.hpp:249
std::string nonlinear_solver_
Nonlinear solver type: newton or nldd.
Definition: BlackoilModelParameters.hpp:372
virtual double simulationTimeElapsed() const =0
Time elapsed since the start of the simulation until the beginning of the current time step [s]...