OGS
NonlinearSolver.cpp
Go to the documentation of this file.
1
11#include "NonlinearSolver.h"
12
13#include <boost/algorithm/string.hpp>
14
15#include "BaseLib/ConfigTree.h"
16#include "BaseLib/Error.h"
17#include "BaseLib/Logging.h"
18#include "BaseLib/MPI.h"
19#include "BaseLib/RunTime.h"
23#include "NumLib/Exceptions.h"
25
26namespace NumLib
27{
28namespace detail
29{
30#if !defined(USE_PETSC) && !defined(USE_LIS)
31bool solvePicard(GlobalLinearSolver& linear_solver, GlobalMatrix& A,
33 MathLib::LinearSolverBehaviour const linear_solver_behaviour)
34{
35 BaseLib::RunTime time_linear_solver;
36 time_linear_solver.start();
37
38 if (!linear_solver.compute(A, linear_solver_behaviour))
39 {
40 ERR("Picard: The linear solver failed in the compute() step.");
41 return false;
42 }
43
44 bool const iteration_succeeded = linear_solver.solve(rhs, x);
45
46 INFO("[time] Linear solver took {:g} s.", time_linear_solver.elapsed());
47
48 if (iteration_succeeded)
49 {
50 return true;
51 }
52
53 ERR("Picard: The linear solver failed in the solve() step.");
54 return false;
55}
56#else
59 MathLib::LinearSolverBehaviour const linear_solver_behaviour)
60{
61 if (linear_solver_behaviour ==
63 linear_solver_behaviour == MathLib::LinearSolverBehaviour::REUSE)
64 {
65 WARN(
66 "The performance optimization to skip the linear solver compute() "
67 "step is not implemented for PETSc or LIS linear solvers.");
68 }
69
70 BaseLib::RunTime time_linear_solver;
71 time_linear_solver.start();
72
73 bool const iteration_succeeded = linear_solver.solve(A, rhs, x);
74
75 INFO("[time] Linear solver took {:g} s.", time_linear_solver.elapsed());
76
77 if (iteration_succeeded)
78 {
79 return true;
80 }
81
82 ERR("Picard: The linear solver failed in the solve() step.");
83 return false;
84}
85#endif
86} // namespace detail
87
90 std::vector<GlobalVector*> const& x,
91 std::vector<GlobalVector*> const& x_prev, int const process_id)
92{
93 if (!_compensate_non_equilibrium_initial_residuum)
94 {
95 return;
96 }
97
98 INFO("Calculate non-equilibrium initial residuum.");
99
102 _equation_system->assemble(x, x_prev, process_id);
103 _equation_system->getA(A);
104 _equation_system->getRhs(*x_prev[process_id], rhs);
105
106 // r_neq = A * x - rhs
108 MathLib::LinAlg::matMult(A, *x[process_id], *_r_neq);
109 MathLib::LinAlg::axpy(*_r_neq, -1.0, rhs); // res -= rhs
110
111 // Set the values of the selected entries of _r_neq, which are associated
112 // with the equations that do not need initial residual compensation, to
113 // zero.
114 const auto selected_global_indices =
115 _equation_system->getIndicesOfResiduumWithoutInitialCompensation();
116
117 std::vector<double> zero_entries(selected_global_indices.size(), 0.0);
118 _r_neq->set(selected_global_indices, zero_entries);
119
121
124}
125
127 std::vector<GlobalVector*>& x,
128 std::vector<GlobalVector*> const& x_prev,
129 std::function<void(int, std::vector<GlobalVector*> const&)> const&
130 postIterationCallback,
131 int const process_id)
132{
133 namespace LinAlg = MathLib::LinAlg;
134 auto& sys = *_equation_system;
135
138
139 std::vector<GlobalVector*> x_new{x};
140 x_new[process_id] =
142 LinAlg::copy(*x[process_id], *x_new[process_id]); // set initial guess
143
144 bool error_norms_met = false;
145
146 _convergence_criterion->preFirstIteration();
147
148 int iteration = 1;
149 for (; iteration <= _maxiter; ++iteration, _convergence_criterion->reset())
150 {
151 BaseLib::RunTime timer_dirichlet;
152 double time_dirichlet = 0.0;
153
154 BaseLib::RunTime time_iteration;
155 time_iteration.start();
156
157 timer_dirichlet.start();
158 auto& x_new_process = *x_new[process_id];
160 sys.computeKnownSolutions(x_new_process, process_id);
161 sys.applyKnownSolutions(x_new_process);
162 time_dirichlet += timer_dirichlet.elapsed();
163
164 sys.preIteration(iteration, x_new_process);
165
166 BaseLib::RunTime time_assembly;
167 time_assembly.start();
168 sys.assemble(x_new, x_prev, process_id);
169 sys.getA(A);
170 sys.getRhs(*x_prev[process_id], rhs);
171
172 // Normalize the linear equation system, if required
173 if (sys.requiresNormalization() &&
174 !_linear_solver.canSolveRectangular())
175 {
176 sys.getAandRhsNormalized(A, rhs);
177 WARN(
178 "The equation system is rectangular, but the current linear "
179 "solver only supports square systems. "
180 "The system will be normalized, which lead to a squared "
181 "condition number and potential numerical issues. "
182 "It is recommended to use a solver that supports rectangular "
183 "equation systems for better numerical stability.");
184 }
185
186 INFO("[time] Assembly took {:g} s.", time_assembly.elapsed());
187
188 // Subtract non-equilibrium initial residuum if set
189 if (_r_neq != nullptr)
190 {
191 LinAlg::axpy(rhs, -1, *_r_neq);
192 }
193
194 timer_dirichlet.start();
195 sys.applyKnownSolutionsPicard(A, rhs, x_new_process);
196 time_dirichlet += timer_dirichlet.elapsed();
197 INFO("[time] Applying Dirichlet BCs took {:g} s.", time_dirichlet);
198
199 if (!sys.isLinear() && _convergence_criterion->hasResidualCheck())
200 {
201 GlobalVector res;
202 LinAlg::matMult(A, x_new_process, res); // res = A * x_new
203 LinAlg::axpy(res, -1.0, rhs); // res -= rhs
204 _convergence_criterion->checkResidual(res);
205 }
206
207 bool iteration_succeeded =
208 detail::solvePicard(_linear_solver, A, rhs, x_new_process,
209 sys.linearSolverNeedsToCompute());
210
211 if (iteration_succeeded)
212 {
213 if (postIterationCallback)
214 {
215 postIterationCallback(iteration, x_new);
216 }
217
218 switch (sys.postIteration(x_new_process))
219 {
221 // Don't copy here. The old x might still be used further
222 // below. Although currently it is not.
223 break;
225 ERR("Picard: The postIteration() hook reported a "
226 "non-recoverable error.");
227 iteration_succeeded = false;
228 // Copy new solution to x.
229 // Thereby the failed solution can be used by the caller for
230 // debugging purposes.
231 LinAlg::copy(x_new_process, *x[process_id]);
232 break;
234 INFO(
235 "Picard: The postIteration() hook decided that this "
236 "iteration has to be repeated.");
238 *x[process_id],
239 x_new_process); // throw the iteration result away
240 continue;
241 }
242 }
243
244 if (!iteration_succeeded)
245 {
246 // Don't compute error norms, break here.
247 error_norms_met = false;
248 break;
249 }
250
251 if (sys.isLinear())
252 {
253 error_norms_met = true;
254 }
255 else
256 {
257 if (_convergence_criterion->hasDeltaXCheck())
258 {
259 GlobalVector minus_delta_x(*x[process_id]);
260 LinAlg::axpy(minus_delta_x, -1.0,
261 x_new_process); // minus_delta_x = x - x_new
262 _convergence_criterion->checkDeltaX(minus_delta_x,
263 x_new_process);
264 }
265
266 error_norms_met = _convergence_criterion->isSatisfied();
267 }
268
269 // Update x s.t. in the next iteration we will compute the right delta x
270 LinAlg::copy(x_new_process, *x[process_id]);
271
272 INFO("[time] Iteration #{:d} took {:g} s.", iteration,
273 time_iteration.elapsed());
274
275 if (error_norms_met)
276 {
277 break;
278 }
279
280 // Avoid increment of the 'iteration' if the error norms are not met,
281 // but maximum number of iterations is reached.
282 if (iteration >= _maxiter)
283 {
284 break;
285 }
286 }
287
288 if (iteration > _maxiter)
289 {
290 ERR("Picard: Could not solve the given nonlinear system within {:d} "
291 "iterations",
292 _maxiter);
293 }
294
298
299 return {error_norms_met, iteration};
300}
301
304 std::vector<GlobalVector*> const& x,
305 std::vector<GlobalVector*> const& x_prev, int const process_id)
306{
307 if (!_compensate_non_equilibrium_initial_residuum)
308 {
309 return;
310 }
311
312 INFO("Calculate non-equilibrium initial residuum.");
313
314 _equation_system->assemble(x, x_prev, process_id);
316 _equation_system->getResidual(*x[process_id], *x_prev[process_id], *_r_neq);
317
318 // Set the values of the selected entries of _r_neq, which are associated
319 // with the equations that do not need initial residual compensation, to
320 // zero.
321 const auto selected_global_indices =
322 _equation_system->getIndicesOfResiduumWithoutInitialCompensation();
323 std::vector<double> zero_entries(selected_global_indices.size(), 0.0);
324
325 _r_neq->set(selected_global_indices, zero_entries);
326
328}
329
331 std::vector<GlobalVector*>& x,
332 std::vector<GlobalVector*> const& x_prev,
333 std::function<void(int, std::vector<GlobalVector*> const&)> const&
334 postIterationCallback,
335 int const process_id)
336{
337 namespace LinAlg = MathLib::LinAlg;
338 auto& sys = *_equation_system;
339
341 auto& minus_delta_x =
344
345 bool error_norms_met = false;
346
347 // TODO be more efficient
348 // init minus_delta_x to the right size
349 LinAlg::copy(*x[process_id], minus_delta_x);
350
351 _convergence_criterion->preFirstIteration();
352
353 int iteration = 1;
354#if !defined(USE_PETSC) && !defined(USE_LIS)
355 int next_iteration_inv_jacobian_recompute = 1;
356#endif
357 for (; iteration <= _maxiter; ++iteration, _convergence_criterion->reset())
358 {
359 BaseLib::RunTime timer_dirichlet;
360 double time_dirichlet = 0.0;
361
362 BaseLib::RunTime time_iteration;
363 time_iteration.start();
364
365 timer_dirichlet.start();
366 sys.computeKnownSolutions(*x[process_id], process_id);
367 time_dirichlet += timer_dirichlet.elapsed();
368
369 sys.preIteration(iteration, *x[process_id]);
370
371 BaseLib::RunTime time_assembly;
372 time_assembly.start();
373 bool mpi_rank_assembly_ok = true;
374 try
375 {
376 sys.assemble(x, x_prev, process_id);
377 }
378 catch (AssemblyException const& e)
379 {
380 ERR("Abort nonlinear iteration. Repeating timestep. Reason: {:s}",
381 e.what());
382 error_norms_met = false;
383 iteration = _maxiter;
384 mpi_rank_assembly_ok = false;
385 }
386 if (BaseLib::MPI::anyOf(!mpi_rank_assembly_ok))
387 {
388 break;
389 }
390 sys.getResidual(*x[process_id], *x_prev[process_id], res);
391 sys.getJacobian(J);
392 INFO("[time] Assembly took {:g} s.", time_assembly.elapsed());
393
394 // Subtract non-equilibrium initial residuum if set
395 if (_r_neq != nullptr)
396 LinAlg::axpy(res, -1, *_r_neq);
397
398 minus_delta_x.setZero();
399
400 timer_dirichlet.start();
401 sys.applyKnownSolutionsNewton(J, res, *x[process_id], minus_delta_x);
402 time_dirichlet += timer_dirichlet.elapsed();
403 INFO("[time] Applying Dirichlet BCs took {:g} s.", time_dirichlet);
404
405 if (!sys.isLinear() && _convergence_criterion->hasResidualCheck())
406 {
407 _convergence_criterion->checkResidual(res);
408 }
409
410 BaseLib::RunTime time_linear_solver;
411 time_linear_solver.start();
412#if !defined(USE_PETSC) && !defined(USE_LIS)
413 auto linear_solver_behaviour = MathLib::LinearSolverBehaviour::REUSE;
414 if (iteration == next_iteration_inv_jacobian_recompute)
415 {
416 linear_solver_behaviour =
418 next_iteration_inv_jacobian_recompute =
419 next_iteration_inv_jacobian_recompute + _recompute_jacobian;
420 }
421 if (!_linear_solver.compute(J, linear_solver_behaviour))
422 {
423 ERR("Newton: The linear solver failed in the compute() step.");
424 }
425 bool iteration_succeeded = _linear_solver.solve(res, minus_delta_x);
426#else
427 bool iteration_succeeded = _linear_solver.solve(J, res, minus_delta_x);
428#endif
429 INFO("[time] Linear solver took {:g} s.", time_linear_solver.elapsed());
430
431 if (!iteration_succeeded)
432 {
433 ERR("Newton: The linear solver failed.");
434 }
435 else
436 {
437 // TODO could be solved in a better way
438 // cf.
439 // https://petsc.org/release/manualpages/Vec/VecWAXPY
440
441 // Copy pointers, replace the one for the given process id.
442 std::vector<GlobalVector*> x_new{x};
443 x_new[process_id] =
445 *x[process_id], _x_new_id);
446 LinAlg::axpy(*x_new[process_id], -_damping, minus_delta_x);
447
448 if (postIterationCallback)
449 {
450 postIterationCallback(iteration, x_new);
451 }
452
453 switch (sys.postIteration(*x_new[process_id]))
454 {
456 break;
458 ERR("Newton: The postIteration() hook reported a "
459 "non-recoverable error.");
460 iteration_succeeded = false;
461 break;
463 INFO(
464 "Newton: The postIteration() hook decided that this "
465 "iteration has to be repeated.");
466 // TODO introduce some onDestroy hook.
468 *x_new[process_id]);
469 continue; // That throws the iteration result away.
470 }
471
472 LinAlg::copy(*x_new[process_id],
473 *x[process_id]); // copy new solution to x
475 *x_new[process_id]);
476 }
477
478 if (!iteration_succeeded)
479 {
480 // Don't compute further error norms, but break here.
481 error_norms_met = false;
482 break;
483 }
484
485 if (sys.isLinear())
486 {
487 error_norms_met = true;
488 }
489 else
490 {
491 if (_convergence_criterion->hasDeltaXCheck())
492 {
493 // Note: x contains the new solution!
494 _convergence_criterion->checkDeltaX(minus_delta_x,
495 *x[process_id]);
496 }
497
498 error_norms_met = _convergence_criterion->isSatisfied();
499 }
500
501 INFO("[time] Iteration #{:d} took {:g} s.", iteration,
502 time_iteration.elapsed());
503
504 if (error_norms_met)
505 {
506 break;
507 }
508
509 // Avoid increment of the 'iteration' if the error norms are not met,
510 // but maximum number of iterations is reached.
511 if (iteration >= _maxiter)
512 {
513 break;
514 }
515 }
516
517 if (iteration > _maxiter)
518 {
519 ERR("Newton: Could not solve the given nonlinear system within {:d} "
520 "iterations",
521 _maxiter);
522 }
523
527
528 return {error_norms_met, iteration};
529}
530
531std::pair<std::unique_ptr<NonlinearSolverBase>, NonlinearSolverTag>
533 BaseLib::ConfigTree const& config)
534{
536 auto const type = config.getConfigParameter<std::string>("type");
538 auto const max_iter = config.getConfigParameter<int>("max_iter");
539
540 if (type == "Picard")
541 {
542 auto const tag = NonlinearSolverTag::Picard;
543 using ConcreteNLS = NonlinearSolver<tag>;
544 return std::make_pair(
545 std::make_unique<ConcreteNLS>(linear_solver, max_iter), tag);
546 }
547 if (type == "Newton")
548 {
550 auto const recompute_jacobian =
551 config.getConfigParameter<int>("recompute_jacobian", 1);
552
554 auto const damping = config.getConfigParameter<double>("damping", 1.0);
555 if (damping <= 0)
556 {
557 OGS_FATAL(
558 "The damping factor for the Newon method must be positive, got "
559 "{:g}.",
560 damping);
561 }
562 auto const tag = NonlinearSolverTag::Newton;
563 using ConcreteNLS = NonlinearSolver<tag>;
564 return std::make_pair(
565 std::make_unique<ConcreteNLS>(linear_solver, max_iter,
566 recompute_jacobian, damping),
567 tag);
568 }
569#ifdef USE_PETSC
570 if (boost::iequals(type, "PETScSNES"))
571 {
572 auto prefix =
574 config.getConfigParameter<std::string>("prefix", "");
575 auto const tag = NonlinearSolverTag::Newton;
576 using ConcreteNLS = PETScNonlinearSolver;
577 return std::make_pair(std::make_unique<ConcreteNLS>(
578 linear_solver, max_iter, std::move(prefix)),
579 tag);
580 }
581
582#endif
583 OGS_FATAL("Unsupported nonlinear solver type '{:s}'.", type.c_str());
584}
585
587{
588 if (_r_neq != nullptr)
589 {
591 }
592}
593
601
602} // namespace NumLib
#define OGS_FATAL(...)
Definition Error.h:26
void INFO(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:35
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:45
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
Definition of the RunTime class.
T getConfigParameter(std::string const &param) const
Count the running time.
Definition RunTime.h:29
double elapsed() const
Get the elapsed time in seconds.
Definition RunTime.h:42
void start()
Start the timer.
Definition RunTime.h:32
bool solve(EigenMatrix &A, EigenVector &b, EigenVector &x)
Global vector based on Eigen vector.
Definition EigenVector.h:25
virtual void releaseMatrix(GlobalMatrix const &A)=0
virtual GlobalMatrix & getMatrix(std::size_t &id)=0
Get an uninitialized matrix with the given id.
virtual GlobalVector & getVector(std::size_t &id)=0
Get an uninitialized vector with the given id.
virtual void releaseVector(GlobalVector const &x)=0
NonlinearSolverTag
Tag used to specify which nonlinear solver will be used.
Definition Types.h:20
std::pair< std::unique_ptr< NonlinearSolverBase >, NonlinearSolverTag > createNonlinearSolver(GlobalLinearSolver &linear_solver, BaseLib::ConfigTree const &config)
static bool anyOf(bool const val, Mpi const &mpi=Mpi{MPI_COMM_WORLD})
Definition MPI.h:187
void finalizeAssembly(PETScMatrix &A)
Definition LinAlg.cpp:198
void copy(PETScVector const &x, PETScVector &y)
Definition LinAlg.cpp:37
void setLocalAccessibleVector(PETScVector const &x)
Definition LinAlg.cpp:27
void matMult(PETScMatrix const &A, PETScVector const &x, PETScVector &y)
Definition LinAlg.cpp:149
void axpy(PETScVector &y, PetscScalar const a, PETScVector const &x)
Definition LinAlg.cpp:57
bool solvePicard(GlobalLinearSolver &linear_solver, GlobalMatrix &A, GlobalVector &rhs, GlobalVector &x, MathLib::LinearSolverBehaviour const linear_solver_behaviour)
static NUMLIB_EXPORT MatrixProvider & provider
static NUMLIB_EXPORT VectorProvider & provider
Status of the non-linear solver.