NonlinearSystemCompositional_impl.hpp
Go to the documentation of this file.
1/*
2 Copyright 2026, SINTEF Digital
3
4 This file is part of the Open Porous Media project (OPM).
5
6 OPM is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 OPM is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with OPM. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#ifndef OPM_NONLINEAR_SYSTEM_COMPOSITIONAL_IMPL_HEADER_INCLUDED
21#define OPM_NONLINEAR_SYSTEM_COMPOSITIONAL_IMPL_HEADER_INCLUDED
22
23#ifndef OPM_NONLINEAR_SYSTEM_COMPOSITIONAL_HEADER_INCLUDED
24#include <config.h>
26#endif
27
28#include <dune/common/timer.hh>
29
30#include <opm/common/ErrorMacros.hpp>
31#include <opm/common/OpmLog/OpmLog.hpp>
32
34
37
38#include <algorithm>
39#include <array>
40#include <cmath>
41#include <stdexcept>
42
43namespace Opm {
44
45template <class TypeTag>
48 const ModelParameters& param,
49 CompWellModel<TypeTag>& wellModel,
50 const bool terminalOutput)
51 : ParentType(simulator, param, wellModel, terminalOutput)
52{
54 || Parameters::Get<Parameters::AcceleratorMode>() != "none") {
55 OPM_THROW(std::invalid_argument,
56 "Compositional runs solve the linear systems on the CPU only."
57 " Please use --linear-solver-accelerator=cpu and --accelerator-mode=none.");
58 }
59
60 this->convergence_reports_.reserve(64);
61}
62
63template <class TypeTag>
67{
69 Dune::Timer perfTimer;
70 perfTimer.start();
71
72 const int lastStepFailed = timer.lastStepFailed();
73 if (this->grid_.comm().size() > 1
74 && this->grid_.comm().max(lastStepFailed) != this->grid_.comm().min(lastStepFailed)) {
75 OPM_THROW(std::runtime_error,
76 "Misalignment of the parallel simulation run in prepareStep "
77 "- the previous step succeeded on some ranks but failed on others.");
78 }
79
80 if (lastStepFailed) {
81 this->wellModel().restoreLastValidState();
82 this->simulator_.model().updateFailed();
83 }
84 else {
85 this->simulator_.model().advanceTimeLevel();
86 }
87
88 this->simulator_.setTime(timer.simulationTimeElapsed());
89 this->simulator_.setTimeStepSize(timer.currentStepLength());
90
91 this->simulator_.problem().resetIterationForNewTimestep();
92 this->simulator_.problem().beginTimeStep();
93
94 report.pre_post_time += perfTimer.stop();
95 return report;
96}
97
98template <class TypeTag>
99void
102 const int minIter,
103 const int maxIter,
104 const SimulatorTimerInterface& timer)
105{
106 ParentType::initialLinearization(report,
107 minIter,
108 maxIter,
109 timer);
110 Dune::Timer perfTimer;
111 perfTimer.start();
112
113 auto convrep = ConvergenceReport{timer.simulationTimeElapsed()};
114 const auto residualMetrics = this->reservoirResidualMetrics();
115 const auto tolerance = this->simulator_.model().newtonMethod().tolerance();
116
117 for (int compIdx = 0; compIdx < numEq; ++compIdx) {
118 const std::array<Scalar, 1> residual{residualMetrics[compIdx]};
119 const std::array<ConvergenceReport::ReservoirFailure::Type, 1> types{
121 };
122 const std::array<Scalar, 1> tolerances{tolerance};
123
124 this->addReservoirConvergenceMetrics(
125 convrep,
126 compIdx,
127 this->compNames_.name(compIdx),
128 residual,
129 types,
130 tolerances,
131 this->param_.max_residual_allowed_,
132 [this](const std::string& message)
133 {
134 if (this->terminal_output_) {
135 OpmLog::debug(message);
136 }
137 });
138 }
139
140 const auto severity = convrep.severityOfWorstFailure();
141 const bool wellConverged = this->wellModel().getWellConvergence();
142 report.converged = convrep.converged() && wellConverged &&
143 this->simulator_.problem().iterationContext().iteration() >= minIter;
144
145 this->convergence_reports_.back().report.push_back(std::move(convrep));
146 report.update_time += perfTimer.stop();
147 this->residual_norms_history_.push_back(residualMetrics);
148
150 this->failureReport_ += report;
151 OPM_THROW_PROBLEM(NumericalProblem, "NaN residual found!");
152 }
153
155 this->failureReport_ += report;
156 OPM_THROW_NOLOG(NumericalProblem, "Too large residual found!");
157 }
158}
159
160template <class TypeTag>
161template <class NonlinearSolverType>
162SimulatorReportSingle
165 NonlinearSolverType& nonlinearSolver)
166{
167 if (this->simulator_.problem().iterationContext().needsTimestepInit()) {
168 this->residual_norms_history_.clear();
169 this->current_relaxation_ = 1.0;
170 this->dx_old_ = 0.0;
171 this->convergence_reports_.push_back({timer.reportStepNum(), timer.currentStepNum(), {}});
172 this->convergence_reports_.back().report.reserve(numEq);
173 }
174
175 auto result = this->nonlinearIterationNewton(timer, nonlinearSolver);
176 this->simulator_.problem().advanceIteration();
177 return result;
178}
179
180template <class TypeTag>
181template <class NonlinearSolverType>
185 NonlinearSolverType& nonlinearSolver)
186{
187 OPM_TIMEFUNCTION();
188
190 Dune::Timer perfTimer;
191
192 this->initialLinearization(report,
193 this->param_.newton_min_iter_,
194 this->param_.newton_max_iter_,
195 timer);
196
197 if (!report.converged) {
198 perfTimer.reset();
199 perfTimer.start();
200 report.total_newton_iterations = 1;
201
202 BVector x(this->simulator_.model().numGridDof());
203 this->linear_solve_setup_time_ = 0.0;
204
205 try {
206 auto& linearizer = this->simulator_.model().linearizer();
207 linearizer.linearizeAuxiliaryEquations();
208 linearizer.finalize();
209
210 this->solveJacobianSystem(x);
211
212 report.linear_solve_setup_time += this->linear_solve_setup_time_;
213 report.linear_solve_time += perfTimer.stop();
214 report.total_linear_iterations += this->linearIterationsLastSolve();
215 }
216 catch (...) {
217 report.linear_solve_setup_time += this->linear_solve_setup_time_;
218 report.linear_solve_time += perfTimer.stop();
219 report.total_linear_iterations += this->linearIterationsLastSolve();
220
221 this->failureReport_ += report;
222 throw;
223 }
224
225 perfTimer.reset();
226 perfTimer.start();
227
228 auto& model = this->simulator_.model();
229 for (unsigned auxModIdx = 0; auxModIdx < model.numAuxiliaryModules(); ++auxModIdx) {
230 model.auxiliaryModule(auxModIdx)->postSolve(x);
231 }
232
233 if (this->param_.use_update_stabilization_) {
234 bool isOscillate = false;
235 bool isStagnate = false;
236 nonlinearSolver.detectOscillations(this->residual_norms_history_,
237 this->residual_norms_history_.size() - 1,
238 isOscillate,
239 isStagnate);
240
241 if (isOscillate) {
242 this->current_relaxation_ -= nonlinearSolver.relaxIncrement();
243 this->current_relaxation_ = std::max(this->current_relaxation_, nonlinearSolver.relaxMax());
244
245 if (this->terminalOutputEnabled()) {
246 OpmLog::info(" Oscillating behavior detected: Relaxation set to "
247 + std::to_string(this->current_relaxation_));
248 }
249 }
250
251 nonlinearSolver.stabilizeNonlinearUpdate(x, this->dx_old_, this->current_relaxation_);
252 }
253
254 this->updateSolution(x);
255 report.update_time += perfTimer.stop();
256 }
257
258 return report;
259}
260
261template <class TypeTag>
264relativeChange() const
265{
266 Scalar resultDelta = 0.0;
267 Scalar resultDenom = 0.0;
268
269 const auto& elemMapper = this->simulator_.model().elementMapper();
270 const auto& gridView = this->simulator_.gridView();
271
272 for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
273 const unsigned globalElemIdx = elemMapper.index(elem);
274 const auto& priVarsNew = this->simulator_.model().solution(/*timeIdx=*/0)[globalElemIdx];
275 const auto& priVarsOld = this->simulator_.model().solution(/*timeIdx=*/1)[globalElemIdx];
276
277 for (int pvIdx = 0; pvIdx < static_cast<int>(priVarsNew.size()); ++pvIdx) {
278 const auto delta = priVarsNew[pvIdx] - priVarsOld[pvIdx];
279 resultDelta += delta * delta;
280 resultDenom += priVarsNew[pvIdx] * priVarsNew[pvIdx];
281 }
282 }
283
284 resultDelta = gridView.comm().sum(resultDelta);
285 resultDenom = gridView.comm().sum(resultDenom);
286
287 return resultDenom > 0.0 ? resultDelta / resultDenom : 0.0;
288}
289
290template <class TypeTag>
291void
294{
295 auto& jacobian = this->simulator_.model().linearizer().jacobian();
296 auto& residual = this->simulator_.model().linearizer().residual();
297 auto& linSolver = this->simulator_.model().newtonMethod().linearSolver();
298
299 // Checked per solve, since wells can open during the run.
300 checkUmfpackMatrixAddWell(linSolver.activeSolverPrm(),
301 this->param_.matrix_add_well_contributions_,
302 !this->wellModel().empty());
303
304 x = 0.0;
305
306 Dune::Timer perfTimer;
307 perfTimer.start();
308 linSolver.prepare(jacobian, residual);
309 this->linear_solve_setup_time_ = perfTimer.stop();
310 linSolver.setResidual(residual);
311 linSolver.getResidual(residual);
312 linSolver.setMatrix(jacobian);
313 linSolver.solve(x);
314}
315
316template <class TypeTag>
317std::vector<typename NonlinearSystemCompositional<TypeTag>::Scalar>
320{
321 const auto& model = this->simulator_.model();
322 const auto& residual = model.linearizer().residual();
323 const auto& constraintsMap = model.linearizer().constraintsMap();
324
325 std::vector<Scalar> residualMetrics(numEq, 0.0);
326
327 // Ghost-cell residuals may omit fluxes from neighbors beyond the overlap.
328 // Use the owning rank's residual to assess convergence.
329 const auto& elemMapper = model.elementMapper();
330 for (const auto& elem : elements(this->simulator_.gridView(), Dune::Partitions::interior)) {
331 const unsigned dofIdx = elemMapper.index(elem);
332 if (dofIdx >= model.numGridDof() || model.dofTotalVolume(dofIdx) <= 0.0) {
333 continue;
334 }
335
336 if (constraintsMap.count(dofIdx) > 0) {
337 continue;
338 }
339
340 const auto& localResidual = residual[dofIdx];
341 for (int eqIdx = 0; eqIdx < numEq; ++eqIdx) {
342 residualMetrics[eqIdx] = std::max(
343 residualMetrics[eqIdx],
344 std::abs(localResidual[eqIdx] * model.eqWeight(dofIdx, eqIdx)));
345 }
346 }
347
348 if (this->grid_.comm().size() > 1 && !residualMetrics.empty()) {
349 this->grid_.comm().max(residualMetrics.data(), residualMetrics.size());
350 }
351
352 return residualMetrics;
353}
354
355} // namespace Opm
356
357#endif // OPM_NONLINEAR_SYSTEM_COMPOSITIONAL_IMPL_HEADER_INCLUDED
Definition: ConvergenceReport.hpp:38
Definition: NonlinearSystemCompositional.hpp:42
SimulatorReportSingle prepareStep(const SimulatorTimerInterface &timer)
Definition: NonlinearSystemCompositional_impl.hpp:66
void initialLinearization(SimulatorReportSingle &report, int minIter, int maxIter, const SimulatorTimerInterface &timer) override
Definition: NonlinearSystemCompositional_impl.hpp:101
typename ParentType::Scalar Scalar
Definition: NonlinearSystemCompositional.hpp:49
NonlinearSystemCompositional(Simulator &simulator, const ModelParameters &param, CompWellModel< TypeTag > &wellModel, bool terminalOutput)
Definition: NonlinearSystemCompositional_impl.hpp:47
Dune::BlockVector< VectorBlockType > BVector
Definition: NonlinearSystemCompositional.hpp:57
Definition: NonlinearSystem.hpp:44
std::vector< StepReport > convergence_reports_
Definition: NonlinearSystem.hpp:170
GetPropType< TypeTag, Properties::Scalar > Scalar
Definition: NonlinearSystem.hpp:51
GetPropType< TypeTag, Properties::Simulator > Simulator
Definition: NonlinearSystem.hpp:47
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
LinearSolverAcceleratorType linearSolverAcceleratorTypeFromCLI()
Converts the CLI option for linear solver accelerator to a LinearSolverAcceleratorType.
Definition: LinearSolverAcceleratorType.hpp:89
Definition: blackoilbioeffectsmodules.hh:45
void checkUmfpackMatrixAddWell(const PropertyTree &prm, bool matrixAddWellContributions, bool hasWells)
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
This file provides the infrastructure to retrieve run-time parameters.
Solver parameters for the NonlinearSystemBlackOilReservoir.
Definition: BlackoilModelParameters.hpp:208
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 pre_post_time
Definition: SimulatorReport.hpp:40
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 total_linear_iterations
Definition: SimulatorReport.hpp:51