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
7#include "BaseLib/Error.h"
8#include "BaseLib/Logging.h"
9#include "BaseLib/MPI.h"
10#include "BaseLib/RunTime.h"
14#include "NumLib/Exceptions.h"
15
16#ifdef USE_PETSC
18#endif // USE_PETSC
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
82namespace
83{
96template <typename Assemble>
97bool assembledOnAllRanks(Assemble const& assemble)
98{
99 bool mpi_rank_assembly_ok = true;
100 try
101 {
102 assemble();
103 }
104 catch (AssemblyException const& e)
105 {
106 ERR("Abort nonlinear iteration. Repeating timestep. Reason: {:s}",
107 e.what());
108 mpi_rank_assembly_ok = false;
109 }
110 return !BaseLib::MPI::anyOf(!mpi_rank_assembly_ok);
111}
112} // namespace
113
116 std::vector<GlobalVector*> const& x,
117 std::vector<GlobalVector*> const& x_prev, int const process_id)
118{
120 {
121 return;
122 }
123
124 INFO("Calculate non-equilibrium initial residuum.");
125
127 auto& rhs = NumLib::GlobalVectorProvider::provider.getVector(_rhs_id);
128 _equation_system->assemble(x, x_prev, process_id);
129 _equation_system->getA(A);
130 _equation_system->getRhs(*x_prev[process_id], rhs);
131
132 // r_neq = A * x - rhs
134 MathLib::LinAlg::matMult(A, *x[process_id], *_r_neq);
135 MathLib::LinAlg::axpy(*_r_neq, -1.0, rhs); // res -= rhs
136
137 // Set the values of the selected entries of _r_neq, which are associated
138 // with the equations that do not need initial residual compensation, to
139 // zero.
140 auto selected_global_indices =
141 _equation_system->getIndicesOfResiduumWithoutInitialCompensation();
142
143#ifdef USE_PETSC
144 // Ghost entry with global index 0 is encoded as -global_size
145 // After abs(), it appears as global_size and must be converted back to 0
146 auto const global_size = _r_neq->size();
147 for (auto& idx : selected_global_indices)
148 {
149 if (idx == global_size)
150 {
151 idx = 0;
152 }
153 }
154#endif
155
156 std::vector<double> zero_entries(selected_global_indices.size(), 0.0);
157 _r_neq->set(selected_global_indices, zero_entries);
158 _equation_system->setReleaseNodalForces(_r_neq, process_id);
159
161
164}
165
167 std::vector<GlobalVector*>& x,
168 std::vector<GlobalVector*> const& x_prev,
169 std::function<void(int, std::vector<GlobalVector*> const&)> const&
170 postIterationCallback,
171 int const process_id)
172{
173 namespace LinAlg = MathLib::LinAlg;
174 auto& sys = *_equation_system;
175
176 if ((_damping != 1.0 ||
178 sys.isLinear())
179 {
180 OGS_FATAL(
181 "Damping (under-relaxation) and Anderson acceleration are not "
182 "compatible with a linear equation system: a single Picard step "
183 "already yields the exact solution, so the mixed/damped iterate "
184 "would be accepted as converged but wrong. Remove the 'damping' "
185 "parameter and the 'anderson' subtree for linear problems.");
186 }
187
189 auto& rhs = NumLib::GlobalVectorProvider::provider.getVector(_rhs_id);
190
191 std::vector<GlobalVector*> x_new{x};
192 x_new[process_id] =
194 LinAlg::copy(*x[process_id], *x_new[process_id]); // set initial guess
195
196 bool error_norms_met = false;
197
198 _convergence_criterion->preFirstIteration();
199
200 // Anderson acceleration of the damped Picard step. Inert for
201 // _anderson_depth below 2 (plain Picard). With beta = _damping = 1 the
202 // stored step reduces to the plain residual g(x) - x; for beta < 1 every
203 // stored step is scaled by beta, which leaves the mixing weights unchanged
204 // (beta cancels).
206
207 int iteration = 1;
208 for (; iteration <= _maxiter; ++iteration, _convergence_criterion->reset())
209 {
210 BaseLib::RunTime timer_dirichlet;
211 double time_dirichlet = 0.0;
212
213 BaseLib::RunTime time_iteration;
214 time_iteration.start();
215
216 INFO("Iteration #{:d} started.", iteration);
217 timer_dirichlet.start();
218 auto& x_new_process = *x_new[process_id];
220 sys.computeKnownSolutions(x_new_process, process_id);
221 sys.applyKnownSolutions(x_new_process);
222 time_dirichlet += timer_dirichlet.elapsed();
223
224 sys.preIteration(iteration, x_new_process);
225
226 BaseLib::RunTime time_assembly;
227 time_assembly.start();
228 if (!assembledOnAllRanks([&]
229 { sys.assemble(x_new, x_prev, process_id); }))
230 {
231 error_norms_met = false;
232 break;
233 }
234 sys.getA(A);
235 sys.getRhs(*x_prev[process_id], rhs);
236
237 // Normalize the linear equation system, if required
238 if (sys.requiresNormalization() &&
239 !_linear_solver.canSolveRectangular())
240 {
241 sys.getAandRhsNormalized(A, rhs);
242 WARN(
243 "The equation system is rectangular, but the current linear "
244 "solver only supports square systems. "
245 "The system will be normalized, which lead to a squared "
246 "condition number and potential numerical issues. "
247 "It is recommended to use a solver that supports rectangular "
248 "equation systems for better numerical stability.");
249 }
250
251 INFO("[time] Assembly took {:g} s.", time_assembly.elapsed());
252
253 // Subtract non-equilibrium initial residuum if set
254 if (_r_neq != nullptr)
255 {
256 LinAlg::axpy(rhs, -1, *_r_neq);
257 }
258
259 auto const solver_needs_to_compute = sys.linearSolverNeedsToCompute();
260 bool const solver_will_compute =
261 _linear_solver.willCompute(solver_needs_to_compute);
262
263 timer_dirichlet.start();
264 sys.applyKnownSolutionsPicard(
265 A, rhs, x_new_process,
266 solver_will_compute
269 FAST_INCOMPLETE_MATRIX_UPDATE);
270 time_dirichlet += timer_dirichlet.elapsed();
271 INFO("[time] Applying Dirichlet BCs took {:g} s.", time_dirichlet);
272
273 if (!sys.isLinear() && _convergence_criterion->hasResidualCheck())
274 {
275 if (!solver_will_compute)
276 {
277 // !solver_will_compute means that the Dirichlet BC application
278 // is incomplete (i.e., A not properly modified) and the
279 // computed residual is wrong.
280 OGS_FATAL(
281 "Logic error. The solver skips the compute step for a "
282 "non-linear equation system.");
283 }
284 GlobalVector res;
285 LinAlg::matMult(A, x_new_process, res); // res = A * x_new
286 LinAlg::axpy(res, -1.0, rhs); // res -= rhs
287 _convergence_criterion->checkResidual(res);
288 }
289
290 bool iteration_succeeded = detail::solvePicard(
291 _linear_solver, A, rhs, x_new_process, solver_needs_to_compute);
292
293 if (iteration_succeeded)
294 {
295 // x_old = x[process_id] (iterate entering this step)
296 // x_new_process (raw Picard output g(x_old))
297 // beta relaxation (always active when damping != 1):
298 // x_new = x_old + beta*(g(x_old) - x_old)
299 // = (1-beta)*x_old + beta*g(x_old)
300 if (_damping != 1.0)
301 {
302 LinAlg::scale(x_new_process, _damping);
303 LinAlg::axpy(x_new_process, 1.0 - _damping, *x[process_id]);
304 }
305 // Anderson acceleration additionally mixes the last damped steps
306 // f_i = beta*(g(x_i) - x_i) (the difference x_new - x_old after
307 // the beta relaxation above); inert when anderson_depth < 2, in
308 // which case only the beta relaxation applies.
309 anderson.accelerate(*x[process_id], x_new_process);
310
311 if (postIterationCallback)
312 {
313 postIterationCallback(iteration, x_new);
314 }
315
316 switch (sys.postIteration(x_new_process))
317 {
319 // Don't copy here. The old x might still be used further
320 // below. Although currently it is not.
321 break;
323 ERR("Picard: The postIteration() hook reported a "
324 "non-recoverable error.");
325 iteration_succeeded = false;
326 // Copy new solution to x.
327 // Thereby the failed solution can be used by the caller for
328 // debugging purposes.
329 LinAlg::copy(x_new_process, *x[process_id]);
330 break;
332 INFO(
333 "Picard: The postIteration() hook decided that this "
334 "iteration has to be repeated.");
336 *x[process_id],
337 x_new_process); // throw the iteration result away
338 anderson.dropLastStep();
339 continue;
340 }
341 }
342
343 if (!iteration_succeeded)
344 {
345 // Don't compute error norms, break here.
346 error_norms_met = false;
347 break;
348 }
349
350 if (sys.isLinear())
351 {
352 error_norms_met = true;
353 }
354 else
355 {
356 if (_convergence_criterion->hasDeltaXCheck())
357 {
358 GlobalVector minus_delta_x(*x[process_id]);
359 LinAlg::axpy(minus_delta_x, -1.0,
360 x_new_process); // minus_delta_x = x - x_new
361 _convergence_criterion->checkDeltaX(minus_delta_x,
362 x_new_process);
363 }
364
365 error_norms_met = _convergence_criterion->isSatisfied();
366 }
367
368 // Update x s.t. in the next iteration we will compute the right delta x
369 LinAlg::copy(x_new_process, *x[process_id]);
370
371 INFO("[time] Iteration #{:d} took {:g} s.", iteration,
372 time_iteration.elapsed());
373
374 if (error_norms_met)
375 {
376 break;
377 }
378
379 // Avoid increment of the 'iteration' if the error norms are not met,
380 // but maximum number of iterations is reached.
381 if (iteration >= _maxiter)
382 {
383 break;
384 }
385 }
386
387 if (iteration > _maxiter)
388 {
389 ERR("Picard: Could not solve the given nonlinear system within {:d} "
390 "iterations",
391 _maxiter);
392 }
393
396 NumLib::GlobalVectorProvider::provider.releaseVector(*x_new[process_id]);
397
398 return {error_norms_met, iteration};
399}
400
403 std::vector<GlobalVector*> const& x,
404 std::vector<GlobalVector*> const& x_prev, int const process_id)
405{
407 {
408 return;
409 }
410
411 INFO("Calculate non-equilibrium initial residuum.");
412
413 _equation_system->assemble(x, x_prev, process_id);
415 _equation_system->getResidual(*x[process_id], *x_prev[process_id], *_r_neq);
416
417 // Set the values of the selected entries of _r_neq, which are associated
418 // with the equations that do not need initial residual compensation, to
419 // zero.
420 auto selected_global_indices =
421 _equation_system->getIndicesOfResiduumWithoutInitialCompensation();
422
423#ifdef USE_PETSC
424 // Ghost entry with global index 0 is encoded as -global_size
425 // After abs(), it appears as global_size and must be converted back to 0
426 auto const global_size = _r_neq->size();
427 for (auto& idx : selected_global_indices)
428 {
429 if (idx == global_size)
430 {
431 idx = 0;
432 }
433 }
434#endif
435
436 std::vector<double> zero_entries(selected_global_indices.size(), 0.0);
437 _r_neq->set(selected_global_indices, zero_entries);
438 _equation_system->setReleaseNodalForces(_r_neq, process_id);
439
441}
442
444 std::vector<GlobalVector*>& x,
445 std::vector<GlobalVector*> const& x_prev,
446 std::function<void(int, std::vector<GlobalVector*> const&)> const&
447 postIterationCallback,
448 int const process_id)
449{
450 namespace LinAlg = MathLib::LinAlg;
451 auto& sys = *_equation_system;
452
453 auto& res = NumLib::GlobalVectorProvider::provider.getVector(_res_id);
454 auto& minus_delta_x =
457
458 bool error_norms_met = false;
459
460 // TODO be more efficient
461 // init minus_delta_x to the right size
462 LinAlg::copy(*x[process_id], minus_delta_x);
463
464 _convergence_criterion->preFirstIteration();
465
466 NewtonStepContext step_ctx{sys, x_prev, process_id};
467
468 int iteration = 1;
469#if !defined(USE_PETSC) && !defined(USE_LIS)
470 int next_iteration_inv_jacobian_recompute = 1;
471#endif
472 for (; iteration <= _maxiter; ++iteration, _convergence_criterion->reset())
473 {
474 BaseLib::RunTime timer_dirichlet;
475 double time_dirichlet = 0.0;
476
477 BaseLib::RunTime time_iteration;
478 INFO("Iteration #{:d} started.", iteration);
479 time_iteration.start();
480
481 timer_dirichlet.start();
482 sys.computeKnownSolutions(*x[process_id], process_id);
483 time_dirichlet += timer_dirichlet.elapsed();
484
485 sys.preIteration(iteration, *x[process_id]);
486
487 BaseLib::RunTime time_assembly;
488 time_assembly.start();
489 if (!assembledOnAllRanks([&] { sys.assemble(x, x_prev, process_id); }))
490 {
491 error_norms_met = false;
492 break;
493 }
494 sys.getResidual(*x[process_id], *x_prev[process_id], res);
495 sys.getJacobian(J);
496 if (_tikhonov_lambda > 0.0 && iteration >= _tikhonov_starting_iteration)
497 {
498 J.addToDiagonal(_tikhonov_lambda);
499 }
500 INFO("[time] Assembly took {:g} s.", time_assembly.elapsed());
501
502 // Subtract non-equilibrium initial residuum if set
503 if (_r_neq != nullptr)
504 {
505 LinAlg::axpy(res, -1, *_r_neq);
506 }
507
508 minus_delta_x.setZero();
509
510 timer_dirichlet.start();
511 sys.applyKnownSolutionsNewton(J, res, *x[process_id], minus_delta_x);
512 time_dirichlet += timer_dirichlet.elapsed();
513 INFO("[time] Applying Dirichlet BCs took {:g} s.", time_dirichlet);
514
515 if (!sys.isLinear() && _convergence_criterion->hasResidualCheck())
516 {
517 _convergence_criterion->checkResidual(res);
518 }
519
520 BaseLib::RunTime time_linear_solver;
521 time_linear_solver.start();
522#if !defined(USE_PETSC) && !defined(USE_LIS)
523 auto linear_solver_behaviour = MathLib::LinearSolverBehaviour::REUSE;
524 if (iteration == next_iteration_inv_jacobian_recompute)
525 {
526 linear_solver_behaviour =
528 next_iteration_inv_jacobian_recompute =
529 next_iteration_inv_jacobian_recompute + _recompute_jacobian;
530 }
531 else if (_tikhonov_lambda > 0.0 &&
532 iteration == _tikhonov_starting_iteration)
533 {
534 // Force a refactorization so the newly added regularization term
535 // is actually used by the linear solve instead of being
536 // discarded by a reused, unregularized factorization.
537 linear_solver_behaviour =
539 }
540
541 bool iteration_succeeded = false;
542 if (!_linear_solver.compute(J, linear_solver_behaviour))
543 {
544 ERR("Newton: The linear solver failed in the compute() step.");
545 }
546 else
547 {
548 iteration_succeeded = _linear_solver.solve(res, minus_delta_x);
549 }
550#else
551 bool iteration_succeeded = _linear_solver.solve(J, res, minus_delta_x);
552#endif
553 INFO("[time] Linear solver took {:g} s.", time_linear_solver.elapsed());
554
555 if (!iteration_succeeded)
556 {
557 ERR("Newton: The linear solver failed.");
558 }
559 else
560 {
561 // TODO could be solved in a better way
562 // cf.
563 // https://petsc.org/release/manualpages/Vec/VecWAXPY
564
565 // Copy pointers, replace the one for the given process id.
566 std::vector<GlobalVector*> x_new{x};
567 x_new[process_id] =
569 *x[process_id], _x_new_id);
570 auto const step_result = _step_strategy->applyStep(
571 *x[process_id], minus_delta_x, res, J, *x_new[process_id],
572 step_ctx, iteration);
573
574 if (step_result.step_length != 1.0)
575 {
576 INFO("Step length: {:g}", step_result.step_length);
577 }
578
579 if (!step_result.success)
580 {
581 ERR("Newton: step strategy failed.");
582 iteration_succeeded = false;
583 }
584 else if (!step_result.x_new_is_set)
585 {
586 LinAlg::axpy(*x_new[process_id], -1.0, minus_delta_x);
587 }
588
589 if (postIterationCallback)
590 {
591 postIterationCallback(iteration, x_new);
592 }
593
594 switch (sys.postIteration(*x_new[process_id]))
595 {
597 break;
599 ERR("Newton: The postIteration() hook reported a "
600 "non-recoverable error.");
601 iteration_succeeded = false;
602 break;
604 INFO(
605 "Newton: The postIteration() hook decided that this "
606 "iteration has to be repeated.");
607 // TODO introduce some onDestroy hook.
609 *x_new[process_id]);
610 continue; // That throws the iteration result away.
611 }
612
613 LinAlg::copy(*x_new[process_id],
614 *x[process_id]); // copy new solution to x
616 *x_new[process_id]);
617 }
618
619 if (!iteration_succeeded)
620 {
621 // Don't compute further error norms, but break here.
622 error_norms_met = false;
623 break;
624 }
625
626 if (sys.isLinear())
627 {
628 error_norms_met = true;
629 }
630 else
631 {
632 if (_convergence_criterion->hasDeltaXCheck())
633 {
634 // Note: x contains the new solution!
635 _convergence_criterion->checkDeltaX(minus_delta_x,
636 *x[process_id]);
637 }
638
639 error_norms_met = _convergence_criterion->isSatisfied();
640 }
641
642 INFO("[time] Iteration #{:d} took {:g} s.", iteration,
643 time_iteration.elapsed());
644
645 if (error_norms_met)
646 {
647 break;
648 }
649
650 // Avoid increment of the 'iteration' if the error norms are not met,
651 // but maximum number of iterations is reached.
652 if (iteration >= _maxiter)
653 {
654 break;
655 }
656 }
657
658 if (iteration > _maxiter)
659 {
660 ERR("Newton: Could not solve the given nonlinear system within {:d} "
661 "iterations",
662 _maxiter);
663 }
664
667 NumLib::GlobalVectorProvider::provider.releaseVector(minus_delta_x);
668
669 return {error_norms_met, iteration};
670}
671
679
687
688} // namespace NumLib
#define OGS_FATAL(...)
Definition Error.h:10
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
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)
void accelerate(GlobalVector const &x_old, GlobalVector &x_new)
static constexpr int min_mixing_depth
ConvergenceCriterion * _convergence_criterion
Convergence criterion used to terminate the Newton iteration.
double _tikhonov_lambda
Tikhonov regularization parameter.
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.
int const _maxiter
maximum number of iterations
int const _recompute_jacobian
Recompute Jacobian every this many steps.
std::unique_ptr< NewtonStepStrategy > _step_strategy
Globalization / step-acceptance strategy (e.g. fixed damping).
NonlinearSolver(GlobalLinearSolver &linear_solver, int const maxiter, std::unique_ptr< NewtonStepStrategy > newton_strategy, int const recompute_jacobian=1)
int _tikhonov_starting_iteration
Starting iteration for Tikhonov regularization.
std::size_t _rhs_id
ID of the right-hand side vector.
GlobalVector * _r_neq
non-equilibrium initial residuum.
NonlinearSolver(GlobalLinearSolver &linear_solver, int const maxiter, int const anderson_depth, double const damping)
int const _maxiter
maximum number of iterations
static bool anyOf(bool const val, Mpi const &mpi=Mpi{OGS_COMM_WORLD})
Definition MPI.h:174
void finalizeAssembly(PETScMatrix &A)
Definition LinAlg.cpp:200
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:151
void scale(PETScVector &x, PetscScalar const a)
Definition LinAlg.cpp:37
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 assembledOnAllRanks(Assemble const &assemble)
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.