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