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 sys.getAandRhsNormalized(A, rhs);
175
176 INFO("[time] Assembly took {:g} s.", time_assembly.elapsed());
177
178 // Subtract non-equilibrium initial residuum if set
179 if (_r_neq != nullptr)
180 {
181 LinAlg::axpy(rhs, -1, *_r_neq);
182 }
183
184 timer_dirichlet.start();
185 sys.applyKnownSolutionsPicard(A, rhs, x_new_process);
186 time_dirichlet += timer_dirichlet.elapsed();
187 INFO("[time] Applying Dirichlet BCs took {:g} s.", time_dirichlet);
188
189 if (!sys.isLinear() && _convergence_criterion->hasResidualCheck())
190 {
191 GlobalVector res;
192 LinAlg::matMult(A, x_new_process, res); // res = A * x_new
193 LinAlg::axpy(res, -1.0, rhs); // res -= rhs
194 _convergence_criterion->checkResidual(res);
195 }
196
197 bool iteration_succeeded =
198 detail::solvePicard(_linear_solver, A, rhs, x_new_process,
199 sys.linearSolverNeedsToCompute());
200
201 if (iteration_succeeded)
202 {
203 if (postIterationCallback)
204 {
205 postIterationCallback(iteration, x_new);
206 }
207
208 switch (sys.postIteration(x_new_process))
209 {
211 // Don't copy here. The old x might still be used further
212 // below. Although currently it is not.
213 break;
215 ERR("Picard: The postIteration() hook reported a "
216 "non-recoverable error.");
217 iteration_succeeded = false;
218 // Copy new solution to x.
219 // Thereby the failed solution can be used by the caller for
220 // debugging purposes.
221 LinAlg::copy(x_new_process, *x[process_id]);
222 break;
224 INFO(
225 "Picard: The postIteration() hook decided that this "
226 "iteration has to be repeated.");
228 *x[process_id],
229 x_new_process); // throw the iteration result away
230 continue;
231 }
232 }
233
234 if (!iteration_succeeded)
235 {
236 // Don't compute error norms, break here.
237 error_norms_met = false;
238 break;
239 }
240
241 if (sys.isLinear())
242 {
243 error_norms_met = true;
244 }
245 else
246 {
247 if (_convergence_criterion->hasDeltaXCheck())
248 {
249 GlobalVector minus_delta_x(*x[process_id]);
250 LinAlg::axpy(minus_delta_x, -1.0,
251 x_new_process); // minus_delta_x = x - x_new
252 _convergence_criterion->checkDeltaX(minus_delta_x,
253 x_new_process);
254 }
255
256 error_norms_met = _convergence_criterion->isSatisfied();
257 }
258
259 // Update x s.t. in the next iteration we will compute the right delta x
260 LinAlg::copy(x_new_process, *x[process_id]);
261
262 INFO("[time] Iteration #{:d} took {:g} s.", iteration,
263 time_iteration.elapsed());
264
265 if (error_norms_met)
266 {
267 break;
268 }
269
270 // Avoid increment of the 'iteration' if the error norms are not met,
271 // but maximum number of iterations is reached.
272 if (iteration >= _maxiter)
273 {
274 break;
275 }
276 }
277
278 if (iteration > _maxiter)
279 {
280 ERR("Picard: Could not solve the given nonlinear system within {:d} "
281 "iterations",
282 _maxiter);
283 }
284
288
289 return {error_norms_met, iteration};
290}
291
294 std::vector<GlobalVector*> const& x,
295 std::vector<GlobalVector*> const& x_prev, int const process_id)
296{
297 if (!_compensate_non_equilibrium_initial_residuum)
298 {
299 return;
300 }
301
302 INFO("Calculate non-equilibrium initial residuum.");
303
304 _equation_system->assemble(x, x_prev, process_id);
306 _equation_system->getResidual(*x[process_id], *x_prev[process_id], *_r_neq);
307
308 // Set the values of the selected entries of _r_neq, which are associated
309 // with the equations that do not need initial residual compensation, to
310 // zero.
311 const auto selected_global_indices =
312 _equation_system->getIndicesOfResiduumWithoutInitialCompensation();
313 std::vector<double> zero_entries(selected_global_indices.size(), 0.0);
314
315 _r_neq->set(selected_global_indices, zero_entries);
316
318}
319
321 std::vector<GlobalVector*>& x,
322 std::vector<GlobalVector*> const& x_prev,
323 std::function<void(int, std::vector<GlobalVector*> const&)> const&
324 postIterationCallback,
325 int const process_id)
326{
327 namespace LinAlg = MathLib::LinAlg;
328 auto& sys = *_equation_system;
329
331 auto& minus_delta_x =
334
335 bool error_norms_met = false;
336
337 // TODO be more efficient
338 // init minus_delta_x to the right size
339 LinAlg::copy(*x[process_id], minus_delta_x);
340
341 _convergence_criterion->preFirstIteration();
342
343 int iteration = 1;
344 for (; iteration <= _maxiter; ++iteration, _convergence_criterion->reset())
345 {
346 BaseLib::RunTime timer_dirichlet;
347 double time_dirichlet = 0.0;
348
349 BaseLib::RunTime time_iteration;
350 time_iteration.start();
351
352 timer_dirichlet.start();
353 sys.computeKnownSolutions(*x[process_id], process_id);
354 time_dirichlet += timer_dirichlet.elapsed();
355
356 sys.preIteration(iteration, *x[process_id]);
357
358 BaseLib::RunTime time_assembly;
359 time_assembly.start();
360 int mpi_rank_assembly_ok = 1;
361 int mpi_all_assembly_ok = 0;
362 try
363 {
364 sys.assemble(x, x_prev, process_id);
365 }
366 catch (AssemblyException const& e)
367 {
368 ERR("Abort nonlinear iteration. Repeating timestep. Reason: {:s}",
369 e.what());
370 error_norms_met = false;
371 iteration = _maxiter;
372 mpi_rank_assembly_ok = 0;
373 }
374 mpi_all_assembly_ok = BaseLib::MPI::reduceMin(mpi_rank_assembly_ok);
375 if (mpi_all_assembly_ok == 0)
376 {
377 break;
378 }
379 sys.getResidual(*x[process_id], *x_prev[process_id], res);
380 sys.getJacobian(J);
381 INFO("[time] Assembly took {:g} s.", time_assembly.elapsed());
382
383 // Subtract non-equilibrium initial residuum if set
384 if (_r_neq != nullptr)
385 LinAlg::axpy(res, -1, *_r_neq);
386
387 minus_delta_x.setZero();
388
389 timer_dirichlet.start();
390 sys.applyKnownSolutionsNewton(J, res, *x[process_id], minus_delta_x);
391 time_dirichlet += timer_dirichlet.elapsed();
392 INFO("[time] Applying Dirichlet BCs took {:g} s.", time_dirichlet);
393
394 if (!sys.isLinear() && _convergence_criterion->hasResidualCheck())
395 {
396 _convergence_criterion->checkResidual(res);
397 }
398
399 BaseLib::RunTime time_linear_solver;
400 time_linear_solver.start();
401 bool iteration_succeeded = _linear_solver.solve(J, res, minus_delta_x);
402 INFO("[time] Linear solver took {:g} s.", time_linear_solver.elapsed());
403
404 if (!iteration_succeeded)
405 {
406 ERR("Newton: The linear solver failed.");
407 }
408 else
409 {
410 // TODO could be solved in a better way
411 // cf.
412 // https://petsc.org/release/manualpages/Vec/VecWAXPY
413
414 // Copy pointers, replace the one for the given process id.
415 std::vector<GlobalVector*> x_new{x};
416 x_new[process_id] =
418 *x[process_id], _x_new_id);
419 LinAlg::axpy(*x_new[process_id], -_damping, minus_delta_x);
420
421 if (postIterationCallback)
422 {
423 postIterationCallback(iteration, x_new);
424 }
425
426 switch (sys.postIteration(*x_new[process_id]))
427 {
429 break;
431 ERR("Newton: The postIteration() hook reported a "
432 "non-recoverable error.");
433 iteration_succeeded = false;
434 break;
436 INFO(
437 "Newton: The postIteration() hook decided that this "
438 "iteration has to be repeated.");
439 // TODO introduce some onDestroy hook.
441 *x_new[process_id]);
442 continue; // That throws the iteration result away.
443 }
444
445 LinAlg::copy(*x_new[process_id],
446 *x[process_id]); // copy new solution to x
448 *x_new[process_id]);
449 }
450
451 if (!iteration_succeeded)
452 {
453 // Don't compute further error norms, but break here.
454 error_norms_met = false;
455 break;
456 }
457
458 if (sys.isLinear())
459 {
460 error_norms_met = true;
461 }
462 else
463 {
464 if (_convergence_criterion->hasDeltaXCheck())
465 {
466 // Note: x contains the new solution!
467 _convergence_criterion->checkDeltaX(minus_delta_x,
468 *x[process_id]);
469 }
470
471 error_norms_met = _convergence_criterion->isSatisfied();
472 }
473
474 INFO("[time] Iteration #{:d} took {:g} s.", iteration,
475 time_iteration.elapsed());
476
477 if (error_norms_met)
478 {
479 break;
480 }
481
482 // Avoid increment of the 'iteration' if the error norms are not met,
483 // but maximum number of iterations is reached.
484 if (iteration >= _maxiter)
485 {
486 break;
487 }
488 }
489
490 if (iteration > _maxiter)
491 {
492 ERR("Newton: Could not solve the given nonlinear system within {:d} "
493 "iterations",
494 _maxiter);
495 }
496
500
501 return {error_norms_met, iteration};
502}
503
504std::pair<std::unique_ptr<NonlinearSolverBase>, NonlinearSolverTag>
506 BaseLib::ConfigTree const& config)
507{
509 auto const type = config.getConfigParameter<std::string>("type");
511 auto const max_iter = config.getConfigParameter<int>("max_iter");
512
513 if (type == "Picard")
514 {
515 auto const tag = NonlinearSolverTag::Picard;
516 using ConcreteNLS = NonlinearSolver<tag>;
517 return std::make_pair(
518 std::make_unique<ConcreteNLS>(linear_solver, max_iter), tag);
519 }
520 if (type == "Newton")
521 {
523 auto const damping = config.getConfigParameter<double>("damping", 1.0);
524 if (damping <= 0)
525 {
526 OGS_FATAL(
527 "The damping factor for the Newon method must be positive, got "
528 "{:g}.",
529 damping);
530 }
531 auto const tag = NonlinearSolverTag::Newton;
532 using ConcreteNLS = NonlinearSolver<tag>;
533 return std::make_pair(
534 std::make_unique<ConcreteNLS>(linear_solver, max_iter, damping),
535 tag);
536 }
537#ifdef USE_PETSC
538 if (boost::iequals(type, "PETScSNES"))
539 {
540 auto prefix =
542 config.getConfigParameter<std::string>("prefix", "");
543 auto const tag = NonlinearSolverTag::Newton;
544 using ConcreteNLS = PETScNonlinearSolver;
545 return std::make_pair(std::make_unique<ConcreteNLS>(
546 linear_solver, max_iter, std::move(prefix)),
547 tag);
548 }
549
550#endif
551 OGS_FATAL("Unsupported nonlinear solver type '{:s}'.", type.c_str());
552}
553
555{
556 if (_r_neq != nullptr)
557 {
559 }
560}
561
569
570} // 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 int reduceMin(int val)
Definition MPI.h:18
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.