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