NonlinearSystemBlackOilReservoir_impl.hpp
Go to the documentation of this file.
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>
30#endif
31
32#include <dune/common/timer.hh>
33
34#include <opm/common/ErrorMacros.hpp>
35#include <opm/common/OpmLog/OpmLog.hpp>
36
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
57namespace {
58 template <typename TypeTag>
59 std::string_view
61 {
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
74namespace Opm {
75
76template <class TypeTag>
79 const ModelParameters& param,
80 typename ParentType::WellModel& 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
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: " +
101 }
102}
103
104template <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
153template <class TypeTag>
154void
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 if (report.converged &&
177 convrep.cnvRelaxSource() != ConvergenceReport::CnvRelaxSource::None)
178 {
180 }
181 ConvergenceReport::Severity severity = convrep.severityOfWorstFailure();
182 this->convergence_reports_.back().report.push_back(std::move(convrep));
183
184 // Throw if any NaN or too large residual found.
186 this->failureReport_ += report;
187 OPM_THROW_PROBLEM(NumericalProblem, "NaN residual found!");
188 } else if (severity == ConvergenceReport::Severity::TooLarge) {
189 this->failureReport_ += report;
190 OPM_THROW_NOLOG(NumericalProblem, "Too large residual found!");
192 this->failureReport_ += report;
193 OPM_THROW_PROBLEM(ConvergenceMonitorFailure,
194 fmt::format(
195 "Total penalty count exceeded cut-off-limit of {}",
196 this->param_.monitor_params_.cutoff_
197 ));
198 }
199 }
200 report.update_time += perfTimer.stop();
201 this->residual_norms_history_.push_back(residual_norms);
202}
203
204template <class TypeTag>
205template <class NonlinearSolverType>
209 NonlinearSolverType& nonlinear_solver)
210{
211 // Model-level timestep initialization (once per timestep).
212 // markTimestepInitialized() is called later in initialLinearization(),
213 // after assembleReservoir() has triggered the well model's prepareTimeStep().
214 if (this->simulator_.problem().iterationContext().needsTimestepInit()) {
215 this->residual_norms_history_.clear();
216 this->conv_monitor_.reset();
217 this->current_relaxation_ = 1.0;
218 this->dx_old_ = 0.0;
219 this->convergence_reports_.push_back({timer.reportStepNum(), timer.currentStepNum(), {}});
220 this->convergence_reports_.back().report.reserve(11);
221 }
222
224 if (this->param_.nonlinear_solver_ != "nldd") {
225 result = this->nonlinearIterationNewton(timer, nonlinear_solver);
226 }
227 else {
228 result = this->nlddSolver_->nonlinearIterationNldd(timer, nonlinear_solver);
229 }
230
231 auto& rst_conv = this->simulator_.problem().eclWriter().mutableOutputModule().getConv();
232 rst_conv.update(this->simulator_.model().linearizer().residual());
233
234 this->simulator_.problem().advanceIteration();
235 return result;
236}
237
238template <class TypeTag>
239template <class NonlinearSolverType>
243 NonlinearSolverType& nonlinear_solver)
244{
245 OPM_TIMEFUNCTION();
246
248 Dune::Timer perfTimer;
249
250 this->initialLinearization(report,
251 this->param_.newton_min_iter_,
252 this->param_.newton_max_iter_,
253 timer);
254
255 if (!report.converged) {
256 perfTimer.reset();
257 perfTimer.start();
258 report.total_newton_iterations = 1;
259
260 const unsigned nc = this->simulator_.model().numGridDof();
261 BVector x(nc);
262
263 linear_solve_setup_time_ = 0.0;
264 try {
265 this->wellModel().linearize(this->simulator().model().linearizer().jacobian(),
266 this->simulator().model().linearizer().residual());
267
268 solveJacobianSystem(x);
269
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 catch (...) {
275 report.linear_solve_setup_time += linear_solve_setup_time_;
276 report.linear_solve_time += perfTimer.stop();
277 report.total_linear_iterations += linearIterationsLastSolve();
278
279 this->failureReport_ += report;
280 throw;
281 }
282
283 perfTimer.reset();
284 perfTimer.start();
285
286 this->wellModel().postSolve(x);
287
288 if (this->param_.use_update_stabilization_) {
289 bool isOscillate = false;
290 bool isStagnate = false;
291 nonlinear_solver.detectOscillations(this->residual_norms_history_,
292 this->residual_norms_history_.size() - 1,
293 isOscillate,
294 isStagnate);
295 if (isOscillate) {
296 this->current_relaxation_ -= nonlinear_solver.relaxIncrement();
297 this->current_relaxation_ = std::max(this->current_relaxation_, nonlinear_solver.relaxMax());
298 // The detector reads reservoir residual history and the response damps the
299 // reservoir update, but the oscillation may originate in the well/group
300 // control layer. Record what was unsatisfied here so the two can be told
301 // apart.
303 if (!this->convergence_reports_.empty() &&
304 !this->convergence_reports_.back().report.empty())
305 {
306 auto& convrep = this->convergence_reports_.back().report.back();
307 source = classifyOscillationSource(convrep);
308 convrep.setOscillationSource(source);
309 }
310 if (this->terminalOutputEnabled()) {
311 OpmLog::info(" Oscillating behavior detected (" + to_string(source)
312 + "): Relaxation set to "
313 + std::to_string(this->current_relaxation_));
314 }
315 }
316 nonlinear_solver.stabilizeNonlinearUpdate(x, this->dx_old_, this->current_relaxation_);
317 }
318
319 this->updateSolution(x);
320 report.update_time += perfTimer.stop();
321 }
322
323 return report;
324}
325
326template <class TypeTag>
329relativeChange() const
330{
331 Scalar resultDelta = 0.0;
332 Scalar resultDenom = 0.0;
333
334 const auto& elemMapper = this->simulator_.model().elementMapper();
335 const auto& gridView = this->simulator_.gridView();
336 for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
337 unsigned globalElemIdx = elemMapper.index(elem);
338 const auto& priVarsNew = this->simulator_.model().solution(/*timeIdx=*/0)[globalElemIdx];
339
340 Scalar pressureNew;
341 pressureNew = priVarsNew[Indices::pressureSwitchIdx];
342
343 Scalar saturationsNew[FluidSystem::numPhases] = { 0.0 };
344 Scalar oilSaturationNew = 1.0;
345 if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx) &&
346 FluidSystem::numActivePhases() > 1 &&
347 priVarsNew.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw)
348 {
349 saturationsNew[FluidSystem::waterPhaseIdx] = priVarsNew[Indices::waterSwitchIdx];
350 oilSaturationNew -= saturationsNew[FluidSystem::waterPhaseIdx];
351 }
352
353 if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx) &&
354 FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx) &&
355 priVarsNew.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg)
356 {
357 assert(Indices::compositionSwitchIdx != std::numeric_limits<unsigned>::max());
358 saturationsNew[FluidSystem::gasPhaseIdx] = priVarsNew[Indices::compositionSwitchIdx];
359 oilSaturationNew -= saturationsNew[FluidSystem::gasPhaseIdx];
360 }
361
362 if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) {
363 saturationsNew[FluidSystem::oilPhaseIdx] = oilSaturationNew;
364 }
365
366 const auto& priVarsOld = this->simulator_.model().solution(/*timeIdx=*/1)[globalElemIdx];
367
368 Scalar pressureOld;
369 pressureOld = priVarsOld[Indices::pressureSwitchIdx];
370
371 Scalar saturationsOld[FluidSystem::numPhases] = { 0.0 };
372 Scalar oilSaturationOld = 1.0;
373
374 // NB fix me! adding pressures changes to saturation changes does not make sense
375 Scalar tmp = pressureNew - pressureOld;
376 resultDelta += tmp*tmp;
377 resultDenom += pressureNew*pressureNew;
378
379 if (FluidSystem::numActivePhases() > 1) {
380 if (priVarsOld.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw) {
381 saturationsOld[FluidSystem::waterPhaseIdx] =
382 priVarsOld[Indices::waterSwitchIdx];
383 oilSaturationOld -= saturationsOld[FluidSystem::waterPhaseIdx];
384 }
385
386 if (priVarsOld.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg)
387 {
388 assert(Indices::compositionSwitchIdx != std::numeric_limits<unsigned>::max());
389 saturationsOld[FluidSystem::gasPhaseIdx] =
390 priVarsOld[Indices::compositionSwitchIdx];
391 oilSaturationOld -= saturationsOld[FluidSystem::gasPhaseIdx];
392 }
393
394 if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) {
395 saturationsOld[FluidSystem::oilPhaseIdx] = oilSaturationOld;
396 }
397 for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++ phaseIdx) {
398 Scalar tmpSat = saturationsNew[phaseIdx] - saturationsOld[phaseIdx];
399 resultDelta += tmpSat*tmpSat;
400 resultDenom += saturationsNew[phaseIdx]*saturationsNew[phaseIdx];
401 assert(std::isfinite(resultDelta));
402 assert(std::isfinite(resultDenom));
403 }
404 }
405 }
406
407 resultDelta = gridView.comm().sum(resultDelta);
408 resultDenom = gridView.comm().sum(resultDenom);
409
410 return resultDenom > 0.0 ? resultDelta / resultDenom : 0.0;
411}
412
413template <class TypeTag>
414void
417{
418 auto& jacobian = this->simulator_.model().linearizer().jacobian().istlMatrix();
419 auto& residual = this->simulator_.model().linearizer().residual();
420 auto& linSolver = this->simulator_.model().newtonMethod().linearSolver();
421
422 const int numSolvers = linSolver.numAvailableSolvers();
423 if (numSolvers > 1 && (linSolver.getSolveCount() % 100 == 0)) {
424 if (this->terminal_output_) {
425 OpmLog::debug("\nRunning speed test for comparing available linear solvers.");
426 }
427
428 Dune::Timer perfTimer;
429 std::vector<double> times(numSolvers);
430 std::vector<double> setupTimes(numSolvers);
431
432 x = 0.0;
433 std::vector<BVector> x_trial(numSolvers, x);
434 for (int solver = 0; solver < numSolvers; ++solver) {
435 linSolver.setActiveSolver(solver);
436 perfTimer.start();
437 linSolver.prepare(jacobian, residual);
438 setupTimes[solver] = perfTimer.stop();
439 perfTimer.reset();
440 linSolver.setResidual(residual);
441 perfTimer.start();
442 linSolver.solve(x_trial[solver]);
443 times[solver] = setupTimes[solver] + perfTimer.stop();
444 perfTimer.reset();
445 if (this->terminal_output_) {
446 OpmLog::debug(fmt::format(fmt::runtime("Solver time {}: {}"), solver, times[solver]));
447 }
448 }
449
450 int fastest_solver = std::ranges::min_element(times) - times.begin();
451 // Use timing on rank 0 to determine fastest, must be consistent across ranks.
452 this->grid_.comm().broadcast(&fastest_solver, 1, 0);
453 this->linear_solve_setup_time_ = setupTimes[fastest_solver];
454 x = x_trial[fastest_solver];
455 linSolver.setActiveSolver(fastest_solver);
456 }
457 else {
458 x = 0.0;
459
460 Dune::Timer perfTimer;
461 perfTimer.start();
462 linSolver.prepare(jacobian, residual);
463 this->linear_solve_setup_time_ = perfTimer.stop();
464 linSolver.setResidual(residual);
465 // actually, the error needs to be calculated after setResidual in order to
466 // account for parallelization properly. since the residual of ECFV
467 // discretizations does not need to be synchronized across processes to be
468 // consistent, this is not relevant for OPM-flow...
469 linSolver.solve(x);
470 }
471}
472
473template <class TypeTag>
474bool
477{
478 return this->param_.tolerance_max_dp_ > 0.0 || this->param_.tolerance_max_ds_ > 0.0
479 || this->param_.tolerance_max_drs_ > 0.0 || this->param_.tolerance_max_drv_ > 0.0;
480}
481
482template <class TypeTag>
483void
486{
487 // Init. solution update vector
488 unsigned nc = this->simulator_.model().numGridDof();
489 solUpd_.resize(nc);
490
491 const auto& elemMapper = this->simulator_.model().elementMapper();
492 const auto& gridView = this->simulator_.gridView();
493 for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
494 // Copy solution vector to transfer primary variables meaning
495 unsigned globalElemIdx = elemMapper.index(elem);
496 solUpd_[globalElemIdx] = this->simulator_.model().solution(/*timeIdx=*/0)[globalElemIdx];
497
498 // Ensure each element is zero
499 std::ranges::fill(solUpd_[globalElemIdx], 0.0);
500 }
501}
502
503template <class TypeTag>
504void
507{
508 const auto& elemMapper = this->simulator_.model().elementMapper();
509 const auto& gridView = this->simulator_.gridView();
510 for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
511 // Get cell vectors
512 unsigned globalElemIdx = elemMapper.index(elem);
513 auto& value = solUpd_[globalElemIdx];
514 const auto& update = dx[globalElemIdx];
515 assert(value.size() == update.size());
516
517 // Transfer update from dx to solution update container (SolutionVector type)
518 std::ranges::copy(update, value.begin());
519 }
520}
521
522template <class TypeTag>
525getMaxSolutionUpdate(const std::vector<unsigned>& ixCells)
526{
527 static constexpr bool enableSolvent =
528 Indices::solventSaturationIdx != std::numeric_limits<unsigned>::max();
529 static constexpr bool enableBrine =
530 Indices::saltConcentrationIdx != std::numeric_limits<unsigned>::max();
531
532 // Init output
533 Scalar dPMax = 0.0;
534 Scalar dSMax = 0.0;
535 Scalar dRsMax = 0.0;
536 Scalar dRvMax = 0.0;
537
538 // Loop over solution update, get the correct variables and calculate max.
539 for (const auto& ix : ixCells) {
540 const auto& value = solUpd_[ix];
541 for (unsigned pvIdx = 0; pvIdx < value.size(); ++pvIdx) {
542 if (pvIdx == Indices::pressureSwitchIdx) {
543 dPMax = std::max(dPMax, std::abs(value[pvIdx]));
544 }
545 else if ((pvIdx == Indices::waterSwitchIdx
546 && value.primaryVarsMeaningWater() == PrimaryVariables::WaterMeaning::Sw)
547 || (pvIdx == Indices::compositionSwitchIdx
548 && value.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Sg)
549 || (enableSolvent && pvIdx == Indices::solventSaturationIdx
550 && value.primaryVarsMeaningSolvent() == PrimaryVariables::SolventMeaning::Ss)
551 || (enableBrine && enableSaltPrecipitation && pvIdx == Indices::saltConcentrationIdx
552 && value.primaryVarsMeaningBrine() == PrimaryVariables::BrineMeaning::Sp) ) {
553 dSMax = std::max(dSMax, std::abs(value[pvIdx]));
554 }
555 else if (pvIdx == Indices::compositionSwitchIdx
556 && value.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rs) {
557 dRsMax = std::max(dRsMax, std::abs(value[pvIdx]));
558 }
559 else if (pvIdx == Indices::compositionSwitchIdx
560 && value.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rv) {
561 dRvMax = std::max(dRvMax, std::abs(value[pvIdx]));
562 }
563 }
564 }
565
566 // Communicate max values
567 dPMax = this->grid_.comm().max(dPMax);
568 dSMax = this->grid_.comm().max(dSMax);
569 dRsMax = this->grid_.comm().max(dRsMax);
570 dRvMax = this->grid_.comm().max(dRvMax);
571
572 return { dPMax, dSMax, dRsMax, dRvMax };
573}
574
575template <class TypeTag>
576std::tuple<typename NonlinearSystemBlackOilReservoir<TypeTag>::Scalar,
580 const Scalar pvSumLocal,
581 const Scalar numAquiferPvSumLocal,
582 std::vector< Scalar >& R_sum,
583 std::vector< Scalar >& maxCoeff,
584 std::vector< Scalar >& B_avg)
585{
586 return ParentType::convergenceReduction(comm,
587 pvSumLocal,
588 numAquiferPvSumLocal,
589 R_sum,
590 maxCoeff,
591 B_avg);
592}
593
594template <class TypeTag>
595std::pair<typename NonlinearSystemBlackOilReservoir<TypeTag>::Scalar,
598localConvergenceData(std::vector<Scalar>& R_sum,
599 std::vector<Scalar>& maxCoeff,
600 std::vector<Scalar>& B_avg,
601 std::vector<int>& maxCoeffCell)
602{
603 OPM_TIMEBLOCK(localConvergenceData);
604 Scalar pvSumLocal = 0.0;
605 Scalar numAquiferPvSumLocal = 0.0;
606 const auto& model = this->simulator_.model();
607 const auto& problem = this->simulator_.problem();
608
609 const auto& residual = this->simulator_.model().linearizer().residual();
610
611 ElementContext elemCtx(this->simulator_);
612 const auto& gridView = this->simulator().gridView();
613 IsNumericalAquiferCell isNumericalAquiferCell(gridView.grid());
615 for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
616 elemCtx.updatePrimaryStencil(elem);
617 elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
618
619 const unsigned cell_idx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
620 const auto& intQuants = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0);
621 const auto& fs = intQuants.fluidState();
622
623 const auto pvValue = problem.referencePorosity(cell_idx, /*timeIdx=*/0) *
624 model.dofTotalVolume(cell_idx);
625 pvSumLocal += pvValue;
626
627 if (isNumericalAquiferCell(elem)) {
628 numAquiferPvSumLocal += pvValue;
629 }
630
631 this->getMaxCoeff(cell_idx, intQuants, fs, residual, pvValue,
632 B_avg, R_sum, maxCoeff, maxCoeffCell);
633 }
634
635 OPM_END_PARALLEL_TRY_CATCH("NonlinearSystemBlackOilReservoir::localConvergenceData() failed: ", this->grid_.comm());
636
637 // compute local average in terms of global number of elements
638 const int bSize = B_avg.size();
639 for (int i = 0; i < bSize; ++i) {
640 B_avg[i] /= Scalar(this->global_nc_);
641 }
642
643 return {pvSumLocal, numAquiferPvSumLocal};
644}
645
646template <class TypeTag>
649characteriseCnvPvSplit(const std::vector<Scalar>& B_avg, const double dt)
650{
651 OPM_TIMEBLOCK(computeCnvErrorPv);
652
653 // 0: cnv <= tolerance_cnv
654 // 1: tolerance_cnv < cnv <= tolerance_cnv_relaxed
655 // 2: tolerance_cnv_relaxed < cnv
656 constexpr auto numPvGroups = std::vector<double>::size_type{3};
657
658 auto cnvPvSplit = std::pair<std::vector<double>, std::vector<int>> {
659 std::piecewise_construct,
660 std::forward_as_tuple(numPvGroups),
661 std::forward_as_tuple(numPvGroups)
662 };
663
664 auto maxCNV = [&B_avg, dt](const auto& residual, const double pvol)
665 {
666 return (dt / pvol) *
667 std::inner_product(residual.begin(), residual.end(),
668 B_avg.begin(), Scalar{0},
669 [](const Scalar m, const auto& x)
670 {
671 using std::abs;
672 return std::max(m, abs(x));
673 }, std::multiplies<>{});
674 };
675
676 auto& [splitPV, cellCntPV] = cnvPvSplit;
677
678 const auto& model = this->simulator().model();
679 const auto& problem = this->simulator().problem();
680 const auto& residual = model.linearizer().residual();
681 const auto& gridView = this->simulator().gridView();
682
683 const IsNumericalAquiferCell isNumericalAquiferCell(gridView.grid());
684
685 ElementContext elemCtx(this->simulator());
686
687 std::vector<unsigned> ixCells;
688
690 for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
691 // Skip cells of numerical Aquifer
692 if (isNumericalAquiferCell(elem)) {
693 continue;
694 }
695
696 elemCtx.updatePrimaryStencil(elem);
697
698 const unsigned cell_idx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
699 const auto pvValue = problem.referencePorosity(cell_idx, /*timeIdx=*/0)
700 * model.dofTotalVolume(cell_idx);
701
702 const auto maxCnv = maxCNV(residual[cell_idx], pvValue);
703
704 const auto ix = (maxCnv > this->param_.tolerance_cnv_)
705 + (maxCnv > this->param_.tolerance_cnv_relaxed_);
706
707 splitPV[ix] += static_cast<double>(pvValue);
708 ++cellCntPV[ix];
709
710 // For dP and dS check, we need cell indices of [1] violations
711 if ( ix > 0 &&
712 (this->param_.tolerance_max_dp_ > 0.0 || this->param_.tolerance_max_ds_ > 0.0
713 || this->param_.tolerance_max_drs_ > 0.0 || this->param_.tolerance_max_drv_ > 0.0 ) ) {
714 ixCells.push_back(cell_idx);
715 }
716 }
717
718 OPM_END_PARALLEL_TRY_CATCH("NonlinearSystemBlackOilReservoir::characteriseCnvPvSplit() failed: ",
719 this->grid_.comm());
720
721 this->grid_.comm().sum(splitPV .data(), splitPV .size());
722 this->grid_.comm().sum(cellCntPV.data(), cellCntPV.size());
723
724 return { cnvPvSplit, ixCells };
725}
726
727template <class TypeTag>
730getReservoirConvergence(const double reportTime,
731 const double dt,
732 const int maxIter,
733 std::vector<Scalar>& B_avg,
734 std::vector<Scalar>& residual_norms)
735{
736 OPM_TIMEBLOCK(getReservoirConvergence);
737 using Vector = std::vector<Scalar>;
738
739 const auto& iterCtx = this->simulator_.problem().iterationContext();
740
741 ConvergenceReport report{reportTime};
742
743 const int numComp = numEq;
744
745 Vector R_sum(numComp, Scalar{0});
746 Vector maxCoeff(numComp, std::numeric_limits<Scalar>::lowest());
747 std::vector<int> maxCoeffCell(numComp, -1);
748
749 const auto [pvSumLocal, numAquiferPvSumLocal] =
750 this->localConvergenceData(R_sum, maxCoeff, B_avg, maxCoeffCell);
751
752 // compute global sum and max of quantities
753 const auto& [pvSum, numAquiferPvSum] =
754 this->convergenceReduction(this->grid_.comm(),
755 pvSumLocal,
756 numAquiferPvSumLocal,
757 R_sum, maxCoeff, B_avg);
758
759 auto cnvSplitData = this->characteriseCnvPvSplit(B_avg, dt);
760 report.setCnvPoreVolSplit(cnvSplitData.cnvPvSplit,
761 pvSum - numAquiferPvSum);
762
763 // For each iteration, we need to determine whether to use the
764 // relaxed tolerances. To disable the usage of relaxed
765 // tolerances, you can set the relaxed tolerances as the strict
766 // tolerances. If min_strict_mb_iter = -1 (default) we use a
767 // relaxed tolerance for the mass balance for the last
768 // iterations. For positive values we use the relaxed tolerance
769 // after the given number of iterations
770 const bool relax_final_iteration_mb =
771 this->param_.min_strict_mb_iter_ < 0 && iterCtx.iteration() == maxIter;
772
773 const bool relax_iter_mb = this->param_.min_strict_mb_iter_ >= 0 &&
774 iterCtx.shouldRelax(this->param_.min_strict_mb_iter_);
775
776 const bool use_relaxed_mb = relax_final_iteration_mb
777 || relax_iter_mb;
778
779 // If min_strict_cnv_iter = -1 we use a relaxed tolerance for
780 // the cnv for the last iterations. For positive values we use
781 // the relaxed tolerance after the given number of iterations.
782 // We also use relaxed tolerances for cells with total
783 // pore-volume less than relaxed_max_pv_fraction_. Default
784 // value of relaxed_max_pv_fraction_ is 0.03
785 const bool relax_final_iteration_cnv =
786 this->param_.min_strict_cnv_iter_ < 0 && iterCtx.iteration() == maxIter;
787
788 const bool relax_iter_cnv = this->param_.min_strict_cnv_iter_ >= 0 &&
789 iterCtx.shouldRelax(this->param_.min_strict_cnv_iter_);
790
791 // Note trailing parentheses here, just before the final
792 // semicolon. This is an immediately invoked function
793 // expression which calculates a single boolean value.
794 const auto relax_pv_fraction_cnv =
795 [&report, this, eligible = pvSum - numAquiferPvSum]()
796 {
797 const auto& cnvPvSplit = report.cnvPvSplit().first;
798
799 // [1]: tol < cnv <= relaxed
800 // [2]: relaxed < cnv
801 Scalar cnvPvSum = static_cast<Scalar>(cnvPvSplit[1] + cnvPvSplit[2]);
802 return cnvPvSum < this->param_.relaxed_max_pv_fraction_ * eligible &&
803 cnvPvSum > 0.0;
804 }();
805
806 // If tolerances for solution changes are met, we use the
807 // relaxed cnv tolerance. Note that all tolerances > 0.0
808 // must be met to enable use of relaxed cnv tolerance.
809 MaxSolutionUpdateData maxSolUpd;
810 const bool use_dp_tol = this->param_.tolerance_max_dp_ > 0.0;
811 const bool use_ds_tol = this->param_.tolerance_max_ds_ > 0.0;
812 const bool use_drs_tol = this->param_.tolerance_max_drs_ > 0.0;
813 const bool use_drv_tol = this->param_.tolerance_max_drv_ > 0.0;
814 const bool use_dsol_tol = use_dp_tol || use_ds_tol || use_drs_tol || use_drv_tol;
815 bool relax_dsol_cnv = false;
816 if (!iterCtx.isFirstGlobalIteration() && use_dsol_tol) {
817 maxSolUpd = getMaxSolutionUpdate(cnvSplitData.ixCells);
818 relax_dsol_cnv =
819 (!use_dp_tol || (maxSolUpd.dPMax > 0.0 && maxSolUpd.dPMax < this->param_.tolerance_max_dp_)) &&
820 (!use_ds_tol || (maxSolUpd.dSMax > 0.0 && maxSolUpd.dSMax < this->param_.tolerance_max_ds_)) &&
821 (!use_drs_tol || (maxSolUpd.dRsMax > 0.0 && maxSolUpd.dRsMax < this->param_.tolerance_max_drs_)) &&
822 (!use_drv_tol || (maxSolUpd.dRvMax > 0.0 && maxSolUpd.dRvMax < this->param_.tolerance_max_drv_));
823 }
824
825 // Determine if relaxed CNV tolerances should be used
826 const bool use_relaxed_cnv = relax_final_iteration_cnv
827 || relax_pv_fraction_cnv
828 || relax_iter_cnv
829 || relax_dsol_cnv;
830
831 // Ensure that CNV convergence criteria is met when max.
832 // solution change tolerances have been fulfilled
833 Scalar tolerance_cnv_relaxed = relax_dsol_cnv ? 1e20 : this->param_.tolerance_cnv_relaxed_;
834
835 const auto tol_cnv = use_relaxed_cnv ? tolerance_cnv_relaxed : this->param_.tolerance_cnv_;
836 const auto tol_mb = use_relaxed_mb ? this->param_.tolerance_mb_relaxed_ : this->param_.tolerance_mb_;
837
838 // Record which condition granted the relaxation, so the accepted state's quality is
839 // visible in the output. Order mirrors the code's own precedence.
840 {
842 const auto source = relax_dsol_cnv ? RS::SolChange
843 : relax_pv_fraction_cnv ? RS::PvFraction
844 : relax_final_iteration_cnv ? RS::FinalIter
845 : relax_iter_cnv ? RS::IterCount
846 : RS::None;
847 report.setCnvRelaxation(source, static_cast<double>(tol_cnv));
848 }
849 const auto tol_cnv_energy = use_relaxed_cnv ? this->param_.tolerance_cnv_energy_relaxed_ : this->param_.tolerance_cnv_energy_;
850 const auto tol_eb = use_relaxed_mb ? this->param_.tolerance_energy_balance_relaxed_ : this->param_.tolerance_energy_balance_;
851
852 // Finish computation
853 std::vector<Scalar> CNV(numComp);
854 std::vector<Scalar> mass_balance_residual(numComp);
855 for (int compIdx = 0; compIdx < numComp; ++compIdx)
856 {
857 CNV[compIdx] = B_avg[compIdx] * dt * maxCoeff[compIdx];
858 mass_balance_residual[compIdx] = std::abs(B_avg[compIdx]*R_sum[compIdx]) * dt / pvSum;
859 residual_norms.push_back(CNV[compIdx]);
860 }
861
862 using CR = ConvergenceReport;
863 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
864 const Scalar res[2] = {
865 mass_balance_residual[compIdx], CNV[compIdx],
866 };
867
868 const CR::ReservoirFailure::Type types[2] = {
869 CR::ReservoirFailure::Type::MassBalance,
870 CR::ReservoirFailure::Type::Cnv,
871 };
872
873 Scalar tol[2] = { tol_mb, tol_cnv, };
874 if (has_energy_ && compIdx == contiEnergyEqIdx) {
875 tol[0] = tol_eb;
876 tol[1] = tol_cnv_energy;
877 }
878
879 this->addReservoirConvergenceMetrics(
880 report,
881 compIdx,
882 this->compNames_.name(compIdx),
883 std::span<const Scalar>{res},
884 std::span<const CR::ReservoirFailure::Type>{types},
885 std::span<const Scalar>{tol},
886 maxResidualAllowed(),
887 [this](const std::string& message)
888 {
889 if (this->terminal_output_) {
890 OpmLog::debug(message);
891 }
892 });
893 }
894
895 // Compute the Newton convergence per cell.
896 this->convergencePerCell(B_avg, dt, tol_cnv, tol_cnv_energy);
897
898 // Output of residuals.
899 if (this->terminal_output_) {
900 // Only rank 0 does print to std::cout
901 if (iterCtx.isFirstGlobalIteration()) {
902 std::string msg = "Iter";
903 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
904 msg += " MB(";
905 msg += this->compNames_.name(compIdx)[0];
906 msg += ") ";
907 }
908
909 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
910 msg += " CNV(";
911 msg += this->compNames_.name(compIdx)[0];
912 msg += ") ";
913 }
914
915 if (use_dsol_tol) {
916 msg += use_dp_tol ? " DP " : "";
917 msg += use_ds_tol ? " DS " : "";
918 msg += use_drs_tol ? " DRS " : "";
919 msg += use_drv_tol ? " DRV " : "";
920 }
921
922 msg += " MBFLAG";
923 msg += " CNVFLAG";
924
925 OpmLog::debug(msg);
926 }
927
928 std::ostringstream ss;
929 const std::streamsize oprec = ss.precision(3);
930 const std::ios::fmtflags oflags = ss.setf(std::ios::scientific);
931
932 ss << std::setw(4) << iterCtx.iteration();
933 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
934 ss << std::setw(11) << mass_balance_residual[compIdx];
935 }
936
937 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
938 ss << std::setw(11) << CNV[compIdx];
939 }
940
941 if (use_dsol_tol) {
942 auto print_dsol =
943 [&] (bool use_tol, Scalar dsol) {
944 if (!use_tol) {
945 return;
946 }
947 if (iterCtx.isFirstGlobalIteration() || dsol <= 0.0) {
948 ss << std::string(5, ' ') << "-" << std::string(5, ' ');
949 }
950 else {
951 ss << std::setw(11) << dsol;
952 }
953 };
954 print_dsol(use_dp_tol, maxSolUpd.dPMax);
955 print_dsol(use_ds_tol, maxSolUpd.dSMax);
956 print_dsol(use_drs_tol, maxSolUpd.dRsMax);
957 print_dsol(use_drv_tol, maxSolUpd.dRvMax);
958 }
959
960 const auto mb_flag = use_relaxed_mb
961 ? DebugFlags::RELAXED
962 : DebugFlags::STRICT;
963
964 const auto cnv_flag = relax_dsol_cnv ?
965 DebugFlags::TUNINGDP
966 : (use_relaxed_cnv
967 ? DebugFlags::RELAXED
968 : DebugFlags::STRICT);
969
970 ss << std::setw(9) << make_string<TypeTag>(mb_flag)
971 << std::setw(9) << make_string<TypeTag>(cnv_flag);
972
973 ss.precision(oprec);
974 ss.flags(oflags);
975
976 OpmLog::debug(ss.str());
977 }
978
979 return report;
980}
981
982template <class TypeTag>
983void
985convergencePerCell(const std::vector<Scalar>& B_avg,
986 const double dt,
987 const double tol_cnv,
988 const double tol_cnv_energy)
989{
990 auto& rst_conv = this->simulator_.problem().eclWriter().mutableOutputModule().getConv();
991 if (!rst_conv.hasConv()) {
992 return;
993 }
994
995 if (this->simulator_.problem().iterationContext().isFirstGlobalIteration()) {
996 rst_conv.prepareConv();
997 }
998
999 const auto& residual = this->simulator_.model().linearizer().residual();
1000 const auto& gridView = this->simulator_.gridView();
1001 const IsNumericalAquiferCell isNumericalAquiferCell(gridView.grid());
1002 ElementContext elemCtx(this->simulator());
1003 std::vector<int> convNewt(residual.size(), 0);
1005 unsigned idx = 0;
1006 const int numComp = B_avg.size();
1007 for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
1008 elemCtx.updatePrimaryStencil(elem);
1009
1010 const unsigned cell_idx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
1011 const auto pvValue = this->simulator_.problem().referencePorosity(cell_idx, /*timeIdx=*/0) *
1012 this->simulator_.model().dofTotalVolume(cell_idx);
1013 for (int compIdx = 0; compIdx < numComp; ++compIdx) {
1014 const auto tol = (has_energy_ && compIdx == contiEnergyEqIdx) ? tol_cnv_energy : tol_cnv;
1015 const Scalar cnv = std::abs(B_avg[compIdx] * residual[cell_idx][compIdx]) * dt / pvValue;
1016 if (std::isnan(cnv) || cnv > maxResidualAllowed() || cnv < 0.0 || cnv > tol) {
1017 convNewt[idx] = 1;
1018 break;
1019 }
1020 }
1021 ++idx;
1022 }
1023 OPM_END_PARALLEL_TRY_CATCH("NonlinearSystemBlackOilReservoir::convergencePerCell() failed: ",
1024 this->grid_.comm());
1025 rst_conv.updateNewton(convNewt);
1026}
1027
1028template <class TypeTag>
1032 const int maxIter,
1033 std::vector<Scalar>& residual_norms)
1034{
1035 OPM_TIMEBLOCK(getConvergence);
1036 // Get convergence reports for reservoir and wells.
1037 std::vector<Scalar> B_avg(numEq, 0.0);
1038 auto report = getReservoirConvergence(timer.simulationTimeElapsed(),
1039 timer.currentStepLength(),
1040 maxIter, B_avg, residual_norms);
1041 {
1042 OPM_TIMEBLOCK(getWellConvergence);
1043 report += this->wellModel().getWellConvergence(B_avg,
1044 /*checkWellGroupControlsAndNetwork*/report.converged());
1045 }
1046
1047 conv_monitor_.checkPenaltyCard(report, this->simulator_.problem().iterationContext().iteration());
1048
1049 return report;
1050}
1051
1052template <class TypeTag>
1053std::vector<std::vector<typename NonlinearSystemBlackOilReservoir<TypeTag>::Scalar> >
1055computeFluidInPlace(const std::vector<int>& /*fipnum*/) const
1056{
1057 OPM_TIMEBLOCK(computeFluidInPlace);
1058 // assert(true)
1059 // return an empty vector
1060 std::vector<std::vector<Scalar> > regionValues(0, std::vector<Scalar>(0,0.0));
1061 return regionValues;
1062}
1063
1064template <class TypeTag>
1065const SimulatorReport&
1068{
1069 if (!hasNlddSolver()) {
1070 OPM_THROW(std::runtime_error, "Cannot get local reports from a model without NLDD solver");
1071 }
1072 return nlddSolver_->localAccumulatedReports();
1073}
1074
1075template <class TypeTag>
1076const std::vector<SimulatorReport>&
1079{
1080 if (!nlddSolver_)
1081 OPM_THROW(std::runtime_error, "Cannot get domain reports from a model without NLDD solver");
1082 return nlddSolver_->domainAccumulatedReports();
1083}
1084
1085template <class TypeTag>
1086void
1088writeNonlinearIterationsPerCell(const std::filesystem::path& odir) const
1089{
1090 if (hasNlddSolver()) {
1091 nlddSolver_->writeNonlinearIterationsPerCell(odir);
1092 }
1093}
1094
1095template <class TypeTag>
1096void
1098writePartitions(const std::filesystem::path& odir) const
1099{
1100 if (hasNlddSolver()) {
1101 nlddSolver_->writePartitions(odir);
1102 return;
1103 }
1104
1105 const auto& elementMapper = this->simulator().model().elementMapper();
1106 const auto& cartMapper = this->simulator().vanguard().cartesianIndexMapper();
1107
1108 const auto& grid = this->simulator().vanguard().grid();
1109 const auto& comm = grid.comm();
1110 const auto nDigit = 1 + static_cast<int>(std::floor(std::log10(comm.size())));
1111
1112 std::ofstream pfile {odir / fmt::format("{1:0>{0}}", nDigit, comm.rank())};
1113
1114 for (const auto& cell : elements(grid.leafGridView(), Dune::Partitions::interior)) {
1115 pfile << comm.rank() << ' '
1116 << cartMapper.cartesianIndex(elementMapper.index(cell)) << ' '
1117 << comm.rank() << '\n';
1118 }
1119}
1120
1121template <class TypeTag>
1122template<class FluidState, class Residual>
1123void
1125getMaxCoeff(const unsigned cell_idx,
1126 const IntensiveQuantities& intQuants,
1127 const FluidState& fs,
1128 const Residual& modelResid,
1129 const Scalar pvValue,
1130 std::vector<Scalar>& B_avg,
1131 std::vector<Scalar>& R_sum,
1132 std::vector<Scalar>& maxCoeff,
1133 std::vector<int>& maxCoeffCell)
1134{
1135 for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx)
1136 {
1137 if (!FluidSystem::phaseIsActive(phaseIdx)) {
1138 continue;
1139 }
1140
1141 const unsigned sIdx = FluidSystem::solventComponentIndex(phaseIdx);
1142 const unsigned compIdx = FluidSystem::canonicalToActiveCompIdx(sIdx);
1143
1144 B_avg[compIdx] += 1.0 / fs.invB(phaseIdx).value();
1145 const auto R2 = modelResid[cell_idx][compIdx];
1146
1147 R_sum[compIdx] += R2;
1148 const Scalar Rval = std::abs(R2) / pvValue;
1149 if (Rval > maxCoeff[compIdx]) {
1150 maxCoeff[compIdx] = Rval;
1151 maxCoeffCell[compIdx] = cell_idx;
1152 }
1153 }
1154
1155 if constexpr (has_solvent_) {
1156 B_avg[contiSolventEqIdx] +=
1157 1.0 / intQuants.solventInverseFormationVolumeFactor().value();
1158 const auto R2 = modelResid[cell_idx][contiSolventEqIdx];
1159 R_sum[contiSolventEqIdx] += R2;
1160 maxCoeff[contiSolventEqIdx] = std::max(maxCoeff[contiSolventEqIdx],
1161 std::abs(R2) / pvValue);
1162 }
1163 if constexpr (has_extbo_) {
1164 B_avg[contiZfracEqIdx] += 1.0 / fs.invB(FluidSystem::gasPhaseIdx).value();
1165 const auto R2 = modelResid[cell_idx][contiZfracEqIdx];
1166 R_sum[ contiZfracEqIdx ] += R2;
1167 maxCoeff[contiZfracEqIdx] = std::max(maxCoeff[contiZfracEqIdx],
1168 std::abs(R2) / pvValue);
1169 }
1170 if constexpr (has_polymer_) {
1171 B_avg[contiPolymerEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1172 const auto R2 = modelResid[cell_idx][contiPolymerEqIdx];
1173 R_sum[contiPolymerEqIdx] += R2;
1174 maxCoeff[contiPolymerEqIdx] = std::max(maxCoeff[contiPolymerEqIdx],
1175 std::abs(R2) / pvValue);
1176 }
1177 if constexpr (has_foam_) {
1178 B_avg[ contiFoamEqIdx ] += 1.0 / fs.invB(FluidSystem::gasPhaseIdx).value();
1179 const auto R2 = modelResid[cell_idx][contiFoamEqIdx];
1180 R_sum[contiFoamEqIdx] += R2;
1181 maxCoeff[contiFoamEqIdx] = std::max(maxCoeff[contiFoamEqIdx],
1182 std::abs(R2) / pvValue);
1183 }
1184 if constexpr (has_brine_) {
1185 B_avg[ contiBrineEqIdx ] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1186 const auto R2 = modelResid[cell_idx][contiBrineEqIdx];
1187 R_sum[contiBrineEqIdx] += R2;
1188 maxCoeff[contiBrineEqIdx] = std::max(maxCoeff[contiBrineEqIdx],
1189 std::abs(R2) / pvValue);
1190 }
1191
1192 if constexpr (has_polymermw_) {
1193 static_assert(has_polymer_);
1194
1195 B_avg[contiPolymerMWEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1196 // the residual of the polymer molecular equation is scaled down by a 100, since molecular weight
1197 // can be much bigger than 1, and this equation shares the same tolerance with other mass balance equations
1198 // TODO: there should be a more general way to determine the scaling-down coefficient
1199 const auto R2 = modelResid[cell_idx][contiPolymerMWEqIdx] / 100.;
1200 R_sum[contiPolymerMWEqIdx] += R2;
1201 maxCoeff[contiPolymerMWEqIdx] = std::max(maxCoeff[contiPolymerMWEqIdx],
1202 std::abs(R2) / pvValue);
1203 }
1204
1205 if constexpr (has_energy_) {
1206 B_avg[contiEnergyEqIdx] += 1.0;
1207 const auto R2 = modelResid[cell_idx][contiEnergyEqIdx];
1208 R_sum[contiEnergyEqIdx] += R2;
1209 maxCoeff[contiEnergyEqIdx] = std::max(maxCoeff[contiEnergyEqIdx],
1210 std::abs(R2) / pvValue);
1211 }
1212
1213 if constexpr (has_bioeffects_) {
1214 B_avg[contiMicrobialEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1215 const auto R1 = modelResid[cell_idx][contiMicrobialEqIdx];
1216 R_sum[contiMicrobialEqIdx] += R1;
1217 maxCoeff[contiMicrobialEqIdx] = std::max(maxCoeff[contiMicrobialEqIdx],
1218 std::abs(R1) / pvValue);
1219 B_avg[contiBiofilmEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1220 const auto R2 = modelResid[cell_idx][contiBiofilmEqIdx];
1221 R_sum[contiBiofilmEqIdx] += R2;
1222 maxCoeff[contiBiofilmEqIdx] = std::max(maxCoeff[contiBiofilmEqIdx],
1223 std::abs(R2) / pvValue);
1224 if constexpr (has_micp_) {
1225 B_avg[contiOxygenEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1226 const auto R3 = modelResid[cell_idx][contiOxygenEqIdx];
1227 R_sum[contiOxygenEqIdx] += R3;
1228 maxCoeff[contiOxygenEqIdx] = std::max(maxCoeff[contiOxygenEqIdx],
1229 std::abs(R3) / pvValue);
1230 B_avg[contiUreaEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1231 const auto R4 = modelResid[cell_idx][contiUreaEqIdx];
1232 R_sum[contiUreaEqIdx] += R4;
1233 maxCoeff[contiUreaEqIdx] = std::max(maxCoeff[contiUreaEqIdx],
1234 std::abs(R4) / pvValue);
1235 B_avg[contiCalciteEqIdx] += 1.0 / fs.invB(FluidSystem::waterPhaseIdx).value();
1236 const auto R5 = modelResid[cell_idx][contiCalciteEqIdx];
1237 R_sum[contiCalciteEqIdx] += R5;
1238 maxCoeff[contiCalciteEqIdx] = std::max(maxCoeff[contiCalciteEqIdx],
1239 std::abs(R5) / pvValue);
1240 }
1241 }
1242}
1243
1244} // namespace Opm
1245
1246#endif // OPM_NONLINEAR_SYSTEM_BLACK_OIL_RESERVOIR_IMPL_HEADER_INCLUDED
#define OPM_END_PARALLEL_TRY_CATCH(prefix, comm)
Catch exception and throw in a parallel try-catch clause.
Definition: DeferredLoggingErrorHelpers.hpp:197
#define OPM_BEGIN_PARALLEL_TRY_CATCH()
Macro to setup the try of a parallel try-catch.
Definition: DeferredLoggingErrorHelpers.hpp:160
Definition: ConvergenceReport.hpp:38
Severity
Definition: ConvergenceReport.hpp:49
CnvRelaxSource
Definition: ConvergenceReport.hpp:246
@ None
strict tolerance applied
void prepareSolutionUpdate() override
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:485
SimulatorReportSingle prepareStep(const SimulatorTimerInterface &timer)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:107
GetPropType< TypeTag, Properties::ElementContext > ElementContext
Definition: NonlinearSystemBlackOilReservoir.hpp:69
void storeSolutionUpdate(const GlobalEqVector &dx) override
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:506
GetPropType< TypeTag, Properties::IntensiveQuantities > IntensiveQuantities
Definition: NonlinearSystemBlackOilReservoir.hpp:70
std::tuple< Scalar, Scalar > convergenceReduction(Parallel::Communication comm, const Scalar pvSumLocal, const Scalar numAquiferPvSumLocal, std::vector< Scalar > &R_sum, std::vector< Scalar > &maxCoeff, std::vector< Scalar > &B_avg)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:579
GetPropType< TypeTag, Properties::Scalar > Scalar
Definition: NonlinearSystemBlackOilReservoir.hpp:78
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:598
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:985
const SimulatorReport & localAccumulatedReports() const
return the statistics of local solves accumulated for this rank
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:1067
SimulatorReportSingle nonlinearIteration(const SimulatorTimerInterface &timer, NonlinearSolverType &nonlinear_solver)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:208
MaxSolutionUpdateData getMaxSolutionUpdate(const std::vector< unsigned > &ixCells)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:525
const std::vector< SimulatorReport > & domainAccumulatedReports() const
return the statistics of local solves accumulated for each domain on this rank
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:1078
bool shouldStoreSolutionUpdate() const override
Get solution update vector as a PrimaryVariable.
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:476
ConvergenceReport getReservoirConvergence(const double reportTime, const double dt, const int maxIter, std::vector< Scalar > &B_avg, std::vector< Scalar > &residual_norms)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:730
Dune::BlockVector< VectorBlockType > BVector
Definition: NonlinearSystemBlackOilReservoir.hpp:113
std::unique_ptr< NonlinearSystemNldd< TypeTag > > nlddSolver_
Non-linear DD solver.
Definition: NonlinearSystemBlackOilReservoir.hpp:306
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:649
ConvergenceReport getConvergence(const SimulatorTimerInterface &timer, const int maxIter, std::vector< Scalar > &residual_norms)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:1031
NonlinearSystemBlackOilReservoir(Simulator &simulator, const ModelParameters &param, typename ParentType::WellModel &well_model, const bool terminal_output)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:78
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:1088
Scalar relativeChange() const
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:329
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
long int global_nc_
The number of cells of the global grid.
Definition: NonlinearSystemBlackOilReservoir.hpp:302
DebugFlags
Definition: NonlinearSystemBlackOilReservoir.hpp:131
void initialLinearization(SimulatorReportSingle &report, const int minIter, const int maxIter, const SimulatorTimerInterface &timer) override
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:156
void getMaxCoeff(const unsigned cell_idx, const IntensiveQuantities &intQuants, const FluidState &fs, const Residual &modelResid, const Scalar pvValue, std::vector< Scalar > &B_avg, std::vector< Scalar > &R_sum, std::vector< Scalar > &maxCoeff, std::vector< int > &maxCoeffCell)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:1125
void solveJacobianSystem(BVector &x)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:416
SimulatorReportSingle nonlinearIterationNewton(const SimulatorTimerInterface &timer, NonlinearSolverType &nonlinear_solver)
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:242
void writePartitions(const std::filesystem::path &odir) const
Definition: NonlinearSystemBlackOilReservoir_impl.hpp:1098
Definition: NonlinearSystem.hpp:44
const Grid & grid_
Definition: NonlinearSystem.hpp:164
std::vector< StepReport > convergence_reports_
Definition: NonlinearSystem.hpp:169
ModelParameters param_
Definition: NonlinearSystem.hpp:166
GetPropType< TypeTag, Properties::Simulator > Simulator
Definition: NonlinearSystem.hpp:47
GetPropType< TypeTag, Properties::GlobalEqVector > GlobalEqVector
Definition: NonlinearSystem.hpp:52
GetPropType< TypeTag, Properties::WellModel > WellModel
Definition: NonlinearSystem.hpp:54
Interface class for SimulatorTimer objects, to be improved.
Definition: SimulatorTimerInterface.hpp:34
virtual int reportStepNum() const
Current report step number. This might differ from currentStepNum in case of sub stepping.
Definition: SimulatorTimerInterface.hpp:109
virtual double currentStepLength() const =0
virtual double simulationTimeElapsed() const =0
virtual int currentStepNum() const =0
Dune::Communication< MPIComm > Communication
Definition: ParallelCommunication.hpp:30
std::size_t countGlobalCells(const Grid &grid)
Get the number of cells of a global grid.
Definition: countGlobalCells.hpp:80
Definition: blackoilbioeffectsmodules.hh:45
ConvergenceReport::OscillationSource classifyOscillationSource(const ConvergenceReport &report)
Classify what was unsatisfied in a report, for oscillation-source reporting.
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
Solver parameters for the NonlinearSystemBlackOilReservoir.
Definition: BlackoilModelParameters.hpp:207
std::string nonlinear_solver_
Nonlinear solver type: newton or nldd.
Definition: BlackoilModelParameters.hpp:378
Definition: AquiferGridUtils.hpp:35
Definition: NonlinearSystemBlackOilReservoir.hpp:118
Definition: NonlinearSystemBlackOilReservoir.hpp:123
Scalar dRsMax
Definition: NonlinearSystemBlackOilReservoir.hpp:126
Scalar dPMax
Definition: NonlinearSystemBlackOilReservoir.hpp:124
Scalar dSMax
Definition: NonlinearSystemBlackOilReservoir.hpp:125
Scalar dRvMax
Definition: NonlinearSystemBlackOilReservoir.hpp:127
Definition: SimulatorReport.hpp:125
A struct for returning timing data from a simulator to its caller.
Definition: SimulatorReport.hpp:34
double linear_solve_time
Definition: SimulatorReport.hpp:43
bool converged
Definition: SimulatorReport.hpp:57
double linear_solve_setup_time
Definition: SimulatorReport.hpp:42
unsigned int total_newton_iterations
Definition: SimulatorReport.hpp:50
double update_time
Definition: SimulatorReport.hpp:45
unsigned int relaxed_cnv_acceptances
Definition: SimulatorReport.hpp:55
unsigned int total_linear_iterations
Definition: SimulatorReport.hpp:51