opm-simulators
NonlinearSystemCompositional_impl.hpp
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>
25 #include <opm/simulators/flow/NonlinearSystemCompositional.hpp>
26 #endif
27 
28 #include <dune/common/timer.hh>
29 
30 #include <opm/common/ErrorMacros.hpp>
31 #include <opm/common/OpmLog/OpmLog.hpp>
32 
33 #include <algorithm>
34 #include <array>
35 #include <cmath>
36 
37 namespace Opm {
38 
39 template <class TypeTag>
40 NonlinearSystemCompositional<TypeTag>::
41 NonlinearSystemCompositional(Simulator& simulator,
42  const ModelParameters& param,
43  CompWellModel<TypeTag>& wellModel,
44  const bool terminalOutput)
45  : ParentType(simulator, param, wellModel, terminalOutput)
46 {
47  this->convergence_reports_.reserve(64);
48 }
49 
50 template <class TypeTag>
51 SimulatorReportSingle
52 NonlinearSystemCompositional<TypeTag>::
53 prepareStep(const SimulatorTimerInterface& timer)
54 {
55  SimulatorReportSingle report;
56  Dune::Timer perfTimer;
57  perfTimer.start();
58 
59  const int lastStepFailed = timer.lastStepFailed();
60  if (this->grid_.comm().size() > 1
61  && this->grid_.comm().max(lastStepFailed) != this->grid_.comm().min(lastStepFailed)) {
62  OPM_THROW(std::runtime_error,
63  "Misalignment of the parallel simulation run in prepareStep "
64  "- the previous step succeeded on some ranks but failed on others.");
65  }
66 
67  if (lastStepFailed) {
68  this->wellModel().restoreLastValidState();
69  this->simulator_.model().updateFailed();
70  }
71  else {
72  this->simulator_.model().advanceTimeLevel();
73  }
74 
75  this->simulator_.setTime(timer.simulationTimeElapsed());
76  this->simulator_.setTimeStepSize(timer.currentStepLength());
77 
78  this->simulator_.problem().resetIterationForNewTimestep();
79  this->simulator_.problem().beginTimeStep();
80 
81  report.pre_post_time += perfTimer.stop();
82  return report;
83 }
84 
85 template <class TypeTag>
86 void
87 NonlinearSystemCompositional<TypeTag>::
88 initialLinearization(SimulatorReportSingle& report,
89  const int minIter,
90  const int maxIter,
91  const SimulatorTimerInterface& timer)
92 {
93  ParentType::initialLinearization(report,
94  minIter,
95  maxIter,
96  timer);
97  Dune::Timer perfTimer;
98  perfTimer.start();
99 
100  auto convrep = ConvergenceReport{timer.simulationTimeElapsed()};
101  const auto residualMetrics = this->reservoirResidualMetrics();
102  const auto tolerance = this->simulator_.model().newtonMethod().tolerance();
103 
104  for (int compIdx = 0; compIdx < numEq; ++compIdx) {
105  const std::array<Scalar, 1> residual{residualMetrics[compIdx]};
106  const std::array<ConvergenceReport::ReservoirFailure::Type, 1> types{
107  ConvergenceReport::ReservoirFailure::Type::MassBalance
108  };
109  const std::array<Scalar, 1> tolerances{tolerance};
110 
111  this->addReservoirConvergenceMetrics(
112  convrep,
113  compIdx,
114  this->compNames_.name(compIdx),
115  residual,
116  types,
117  tolerances,
118  this->param_.max_residual_allowed_,
119  [this](const std::string& message)
120  {
121  if (this->terminal_output_) {
122  OpmLog::debug(message);
123  }
124  });
125  }
126 
127  const auto severity = convrep.severityOfWorstFailure();
128  const bool wellConverged = this->wellModel().getWellConvergence();
129  report.converged = convrep.converged() && wellConverged &&
130  this->simulator_.problem().iterationContext().iteration() >= minIter;
131 
132  this->convergence_reports_.back().report.push_back(std::move(convrep));
133  report.update_time += perfTimer.stop();
134  this->residual_norms_history_.push_back(residualMetrics);
135 
136  if (severity == ConvergenceReport::Severity::NotANumber) {
137  this->failureReport_ += report;
138  OPM_THROW_PROBLEM(NumericalProblem, "NaN residual found!");
139  }
140 
141  if (severity == ConvergenceReport::Severity::TooLarge) {
142  this->failureReport_ += report;
143  OPM_THROW_NOLOG(NumericalProblem, "Too large residual found!");
144  }
145 }
146 
147 template <class TypeTag>
148 template <class NonlinearSolverType>
149 SimulatorReportSingle
150 NonlinearSystemCompositional<TypeTag>::
151 nonlinearIteration(const SimulatorTimerInterface& timer,
152  NonlinearSolverType& nonlinearSolver)
153 {
154  if (this->simulator_.problem().iterationContext().needsTimestepInit()) {
155  this->residual_norms_history_.clear();
156  this->current_relaxation_ = 1.0;
157  this->dx_old_ = 0.0;
158  this->convergence_reports_.push_back({timer.reportStepNum(), timer.currentStepNum(), {}});
159  this->convergence_reports_.back().report.reserve(numEq);
160  }
161 
162  auto result = this->nonlinearIterationNewton(timer, nonlinearSolver);
163  this->simulator_.problem().advanceIteration();
164  return result;
165 }
166 
167 template <class TypeTag>
168 template <class NonlinearSolverType>
169 SimulatorReportSingle
170 NonlinearSystemCompositional<TypeTag>::
171 nonlinearIterationNewton(const SimulatorTimerInterface& timer,
172  NonlinearSolverType& nonlinearSolver)
173 {
174  OPM_TIMEFUNCTION();
175 
176  SimulatorReportSingle report;
177  Dune::Timer perfTimer;
178 
179  this->initialLinearization(report,
180  this->param_.newton_min_iter_,
181  this->param_.newton_max_iter_,
182  timer);
183 
184  if (!report.converged) {
185  perfTimer.reset();
186  perfTimer.start();
187  report.total_newton_iterations = 1;
188 
189  BVector x(this->simulator_.model().numGridDof());
190  this->linear_solve_setup_time_ = 0.0;
191 
192  try {
193  auto& linearizer = this->simulator_.model().linearizer();
194  linearizer.linearizeAuxiliaryEquations();
195  linearizer.finalize();
196 
197  this->solveJacobianSystem(x);
198 
199  report.linear_solve_setup_time += this->linear_solve_setup_time_;
200  report.linear_solve_time += perfTimer.stop();
201  report.total_linear_iterations += this->linearIterationsLastSolve();
202  }
203  catch (...) {
204  report.linear_solve_setup_time += this->linear_solve_setup_time_;
205  report.linear_solve_time += perfTimer.stop();
206  report.total_linear_iterations += this->linearIterationsLastSolve();
207 
208  this->failureReport_ += report;
209  throw;
210  }
211 
212  perfTimer.reset();
213  perfTimer.start();
214 
215  auto& model = this->simulator_.model();
216  for (unsigned auxModIdx = 0; auxModIdx < model.numAuxiliaryModules(); ++auxModIdx) {
217  model.auxiliaryModule(auxModIdx)->postSolve(x);
218  }
219 
220  if (this->param_.use_update_stabilization_) {
221  bool isOscillate = false;
222  bool isStagnate = false;
223  nonlinearSolver.detectOscillations(this->residual_norms_history_,
224  this->residual_norms_history_.size() - 1,
225  isOscillate,
226  isStagnate);
227 
228  if (isOscillate) {
229  this->current_relaxation_ -= nonlinearSolver.relaxIncrement();
230  this->current_relaxation_ = std::max(this->current_relaxation_, nonlinearSolver.relaxMax());
231 
232  if (this->terminalOutputEnabled()) {
233  OpmLog::info(" Oscillating behavior detected: Relaxation set to "
234  + std::to_string(this->current_relaxation_));
235  }
236  }
237 
238  nonlinearSolver.stabilizeNonlinearUpdate(x, this->dx_old_, this->current_relaxation_);
239  }
240 
241  this->updateSolution(x);
242  report.update_time += perfTimer.stop();
243  }
244 
245  return report;
246 }
247 
248 template <class TypeTag>
249 typename NonlinearSystemCompositional<TypeTag>::Scalar
250 NonlinearSystemCompositional<TypeTag>::
251 relativeChange() const
252 {
253  Scalar resultDelta = 0.0;
254  Scalar resultDenom = 0.0;
255 
256  const auto& elemMapper = this->simulator_.model().elementMapper();
257  const auto& gridView = this->simulator_.gridView();
258 
259  for (const auto& elem : elements(gridView, Dune::Partitions::interior)) {
260  const unsigned globalElemIdx = elemMapper.index(elem);
261  const auto& priVarsNew = this->simulator_.model().solution(/*timeIdx=*/0)[globalElemIdx];
262  const auto& priVarsOld = this->simulator_.model().solution(/*timeIdx=*/1)[globalElemIdx];
263 
264  for (int pvIdx = 0; pvIdx < static_cast<int>(priVarsNew.size()); ++pvIdx) {
265  const auto delta = priVarsNew[pvIdx] - priVarsOld[pvIdx];
266  resultDelta += delta * delta;
267  resultDenom += priVarsNew[pvIdx] * priVarsNew[pvIdx];
268  }
269  }
270 
271  resultDelta = gridView.comm().sum(resultDelta);
272  resultDenom = gridView.comm().sum(resultDenom);
273 
274  return resultDenom > 0.0 ? resultDelta / resultDenom : 0.0;
275 }
276 
277 template <class TypeTag>
278 void
279 NonlinearSystemCompositional<TypeTag>::
280 solveJacobianSystem(BVector& x)
281 {
282  auto& jacobian = this->simulator_.model().linearizer().jacobian();
283  auto& residual = this->simulator_.model().linearizer().residual();
284  auto& linSolver = this->simulator_.model().newtonMethod().linearSolver();
285 
286  x = 0.0;
287 
288  Dune::Timer perfTimer;
289  perfTimer.start();
290  linSolver.prepare(jacobian, residual);
291  this->linear_solve_setup_time_ = perfTimer.stop();
292  linSolver.setResidual(residual);
293  linSolver.getResidual(residual);
294  linSolver.setMatrix(jacobian);
295  linSolver.solve(x);
296 }
297 
298 template <class TypeTag>
299 std::vector<typename NonlinearSystemCompositional<TypeTag>::Scalar>
300 NonlinearSystemCompositional<TypeTag>::
301 reservoirResidualMetrics() const
302 {
303  const auto& model = this->simulator_.model();
304  const auto& residual = model.linearizer().residual();
305  const auto& constraintsMap = model.linearizer().constraintsMap();
306 
307  std::vector<Scalar> residualMetrics(numEq, 0.0);
308 
309  for (unsigned dofIdx = 0; dofIdx < residual.size(); ++dofIdx) {
310  if (dofIdx >= model.numGridDof() || model.dofTotalVolume(dofIdx) <= 0.0) {
311  continue;
312  }
313 
314  if (constraintsMap.count(dofIdx) > 0) {
315  continue;
316  }
317 
318  const auto& localResidual = residual[dofIdx];
319  for (int eqIdx = 0; eqIdx < numEq; ++eqIdx) {
320  residualMetrics[eqIdx] = std::max(
321  residualMetrics[eqIdx],
322  std::abs(localResidual[eqIdx] * model.eqWeight(dofIdx, eqIdx)));
323  }
324  }
325 
326  if (this->grid_.comm().size() > 1 && !residualMetrics.empty()) {
327  this->grid_.comm().max(residualMetrics.data(), residualMetrics.size());
328  }
329 
330  return residualMetrics;
331 }
332 
333 } // namespace Opm
334 
335 #endif // OPM_NONLINEAR_SYSTEM_COMPOSITIONAL_IMPL_HEADER_INCLUDED
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45