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