LCOV - code coverage report
Current view: top level - src/convergence - DefaultNonlinearConvergence.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #33416 (b10b36) with base 9fbd27 Lines: 109 121 90.1 %
Date: 2026-07-23 16:15:30 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : // MOOSE includes
      11             : #include "DefaultNonlinearConvergence.h"
      12             : #include "FEProblemBase.h"
      13             : #include "PetscSupport.h"
      14             : #include "NonlinearSystemBase.h"
      15             : #include "ConvergenceIterationTypes.h"
      16             : 
      17             : #include "libmesh/equation_systems.h"
      18             : 
      19             : // PETSc includes
      20             : #include <petsc.h>
      21             : #include <petscmat.h>
      22             : #include <petscsnes.h>
      23             : 
      24             : registerMooseObject("MooseApp", DefaultNonlinearConvergence);
      25             : 
      26             : InputParameters
      27      132997 : DefaultNonlinearConvergence::validParams()
      28             : {
      29      132997 :   InputParameters params = DefaultConvergenceBase::validParams();
      30      132997 :   params += FEProblemSolve::feProblemDefaultConvergenceParams();
      31             : 
      32      398991 :   params.addPrivateParam<bool>("added_as_default", false);
      33             : 
      34      132997 :   params.addClassDescription("Default convergence criteria for FEProblem.");
      35             : 
      36      132997 :   return params;
      37           0 : }
      38             : 
      39       66244 : DefaultNonlinearConvergence::DefaultNonlinearConvergence(const InputParameters & parameters)
      40             :   : DefaultConvergenceBase(parameters),
      41      198732 :     _fe_problem(*getCheckedPointerParam<FEProblemBase *>("_fe_problem_base")),
      42      132488 :     _nl_abs_div_tol(getSharedExecutionerParam<Real>("nl_abs_div_tol")),
      43      132488 :     _nl_rel_div_tol(getSharedExecutionerParam<Real>("nl_div_tol")),
      44       66244 :     _div_threshold(std::numeric_limits<Real>::max()),
      45      132488 :     _nl_forced_its(getSharedExecutionerParam<unsigned int>("nl_forced_its")),
      46      132488 :     _nl_max_pingpong(getSharedExecutionerParam<unsigned int>("n_max_nonlinear_pingpong")),
      47       66244 :     _nl_current_pingpong(0)
      48             : {
      49       66244 :   EquationSystems & es = _fe_problem.es();
      50             : 
      51       66244 :   es.parameters.set<unsigned int>("nonlinear solver maximum iterations") =
      52      198732 :       getSharedExecutionerParam<unsigned int>("nl_max_its");
      53       66244 :   es.parameters.set<unsigned int>("nonlinear solver maximum function evaluations") =
      54      198732 :       getSharedExecutionerParam<unsigned int>("nl_max_funcs");
      55       66244 :   es.parameters.set<Real>("nonlinear solver absolute residual tolerance") =
      56      198732 :       getSharedExecutionerParam<Real>("nl_abs_tol");
      57       66244 :   es.parameters.set<Real>("nonlinear solver relative residual tolerance") =
      58      198732 :       getSharedExecutionerParam<Real>("nl_rel_tol");
      59       66244 :   es.parameters.set<Real>("nonlinear solver divergence tolerance") =
      60      198732 :       getSharedExecutionerParam<Real>("nl_div_tol");
      61       66244 :   es.parameters.set<Real>("nonlinear solver absolute step tolerance") =
      62      198732 :       getSharedExecutionerParam<Real>("nl_abs_step_tol");
      63       66244 :   es.parameters.set<Real>("nonlinear solver relative step tolerance") =
      64      198732 :       getSharedExecutionerParam<Real>("nl_rel_step_tol");
      65       66244 : }
      66             : 
      67             : void
      68       56549 : DefaultNonlinearConvergence::checkIterationType(IterationType it_type) const
      69             : {
      70       56549 :   DefaultConvergenceBase::checkIterationType(it_type);
      71             : 
      72       56549 :   if (it_type != ConvergenceIterationTypes::NONLINEAR)
      73           3 :     mooseError("DefaultNonlinearConvergence can only be used with nonlinear solves.");
      74       56546 : }
      75             : 
      76             : NonlinearSystemBase &
      77      732321 : DefaultNonlinearConvergence::nonlinearSystem()
      78             : {
      79      732321 :   return _fe_problem.currentNonlinearSystem();
      80             : }
      81             : 
      82             : bool
      83      731431 : DefaultNonlinearConvergence::checkResidualConvergence(const unsigned int n_iter,
      84             :                                                       const Real fnorm,
      85             :                                                       const Real ref_norm,
      86             :                                                       const Real rel_tol,
      87             :                                                       const Real abs_tol,
      88             :                                                       std::ostringstream & oss)
      89             : {
      90      731431 :   if (fnorm < abs_tol)
      91             :   {
      92      144588 :     oss << "Converged due to absolute residual " << fnorm << " < absolute tolerance (" << abs_tol
      93      144588 :         << ")\n";
      94      144588 :     return true;
      95             :   }
      96      586843 :   else if (n_iter && fnorm <= ref_norm * rel_tol)
      97             :   {
      98      139516 :     oss << "Converged due to relative/normalized residual norm " << fnorm / ref_norm
      99      139516 :         << " < relative tolerance (" << rel_tol << ")\n";
     100      139516 :     return true;
     101             :   }
     102             :   else
     103      447327 :     return false;
     104             : }
     105             : 
     106             : Convergence::MooseConvergenceStatus
     107      750624 : DefaultNonlinearConvergence::checkConvergence(unsigned int n_iter)
     108             : {
     109      750624 :   TIME_SECTION(_perfid_check_convergence);
     110             : 
     111      750624 :   NonlinearSystemBase & system = nonlinearSystem();
     112      750624 :   MooseConvergenceStatus status = MooseConvergenceStatus::ITERATING;
     113             : 
     114      750624 :   SNES snes = system.getSNES();
     115             : 
     116             :   // ||u||
     117             :   PetscReal xnorm;
     118      750624 :   LibmeshPetscCallA(_fe_problem.comm().get(), SNESGetSolutionNorm(snes, &xnorm));
     119             : 
     120             :   // ||r||
     121             :   PetscReal fnorm;
     122      750624 :   LibmeshPetscCallA(_fe_problem.comm().get(), SNESGetFunctionNorm(snes, &fnorm));
     123             : 
     124             :   // ||du||
     125             :   PetscReal snorm;
     126      750624 :   LibmeshPetscCallA(_fe_problem.comm().get(), SNESGetUpdateNorm(snes, &snorm));
     127             : 
     128             :   // Get current number of function evaluations done by SNES
     129             :   PetscInt nfuncs;
     130      750624 :   LibmeshPetscCallA(_fe_problem.comm().get(), SNESGetNumberFunctionEvals(snes, &nfuncs));
     131             : 
     132             :   // Get tolerances from SNES
     133             :   PetscReal rel_step_tol;
     134             :   PetscInt max_its, max_funcs;
     135      750624 :   LibmeshPetscCallA(
     136             :       _fe_problem.comm().get(),
     137             :       SNESGetTolerances(snes, &_abs_tol, &_rel_tol, &rel_step_tol, &max_its, &max_funcs));
     138             : 
     139             : #if !PETSC_VERSION_LESS_THAN(3, 8, 4)
     140      750624 :   PetscBool force_iteration = PETSC_FALSE;
     141      750624 :   LibmeshPetscCallA(_fe_problem.comm().get(), SNESGetForceIteration(snes, &force_iteration));
     142             : 
     143             :   // if PETSc says to force iteration, then force at least one iteration
     144      750624 :   if (force_iteration && !(_nl_forced_its))
     145         734 :     _nl_forced_its = 1;
     146             : 
     147             :   // if specified here to force iteration, but PETSc doesn't know, tell it
     148      750624 :   if (!force_iteration && (_nl_forced_its))
     149             :   {
     150         164 :     LibmeshPetscCallA(_fe_problem.comm().get(), SNESSetForceIteration(snes, PETSC_TRUE));
     151             :   }
     152             : #endif
     153             : 
     154             :   // See if SNESSetFunctionDomainError() has been called.
     155             :   // Note: SNESSetFunctionDomainError() and SNESGetFunctionDomainError() were added
     156             :   // in different releases of PETSc. The latter was removed in PETSc 3.25.0.
     157             : #if PETSC_VERSION_LESS_THAN(3, 25, 0)
     158             :   PetscBool domainerror;
     159             :   LibmeshPetscCallA(_fe_problem.comm().get(), SNESGetFunctionDomainError(snes, &domainerror));
     160             :   if (domainerror)
     161             :     status = MooseConvergenceStatus::DIVERGED;
     162             : #else
     163             :   SNESConvergedReason reason;
     164      750624 :   LibmeshPetscCallA(_fe_problem.comm().get(), SNESGetConvergedReason(snes, &reason));
     165      750624 :   if (reason == SNES_DIVERGED_FUNCTION_DOMAIN)
     166           0 :     status = MooseConvergenceStatus::DIVERGED;
     167             : #endif
     168             : 
     169             :   // Needed by ResidualReferenceConvergence
     170      750624 :   nonlinearConvergenceSetup();
     171             : 
     172             :   Real fnorm_old;
     173             :   // This is the first residual before any iterations have been done, but after
     174             :   // solution-modifying objects (if any) have been imposed on the solution vector.
     175             :   // We save it, and use it to detect convergence if system.usePreSMOResidual() == false.
     176      750624 :   if (n_iter == 0)
     177             :   {
     178      286424 :     system.setInitialResidual(fnorm);
     179      286424 :     fnorm_old = fnorm;
     180      286424 :     _nl_current_pingpong = 0;
     181             :   }
     182             :   else
     183      464200 :     fnorm_old = system._last_nl_rnorm;
     184             : 
     185             :   // Check for nonlinear residual ping-pong.
     186             :   // Ping-pong will always start from a residual increase
     187      750624 :   if ((_nl_current_pingpong % 2 == 1 && !(fnorm > fnorm_old)) ||
     188      746300 :       (_nl_current_pingpong % 2 == 0 && fnorm > fnorm_old))
     189        9112 :     _nl_current_pingpong += 1;
     190             :   else
     191      741512 :     _nl_current_pingpong = 0;
     192             : 
     193      750624 :   const auto ref_residual = system.referenceResidual();
     194      750624 :   std::ostringstream oss;
     195      750624 :   if (n_iter < _nl_forced_its)
     196        1536 :     oss << "Number of forced iterations not yet reached: " << n_iter << " < " << _nl_forced_its
     197        1536 :         << '\n';
     198      749088 :   else if (fnorm != fnorm)
     199             :   {
     200           0 :     oss << "Failed to converge, residual norm is NaN\n";
     201           0 :     status = MooseConvergenceStatus::DIVERGED;
     202             :   }
     203      749088 :   else if (checkResidualConvergence(n_iter, fnorm, ref_residual, _rel_tol, _abs_tol, oss))
     204      285837 :     status = MooseConvergenceStatus::CONVERGED;
     205      463251 :   else if (nfuncs >= max_funcs)
     206             :   {
     207           0 :     oss << "Exceeded maximum number of residual evaluations: " << nfuncs << " > " << max_funcs
     208           0 :         << '\n';
     209           0 :     status = MooseConvergenceStatus::DIVERGED;
     210             :   }
     211      463251 :   else if ((n_iter >= _nl_forced_its) && n_iter && fnorm > system._last_nl_rnorm &&
     212        5125 :            fnorm >= _div_threshold)
     213             :   {
     214           0 :     oss << "Nonlinear solve was blowing up!\n";
     215           0 :     status = MooseConvergenceStatus::DIVERGED;
     216             :   }
     217      463251 :   else if (snorm < rel_step_tol * xnorm)
     218             :   {
     219           0 :     oss << "Converged due to small update length: " << snorm << " < " << rel_step_tol << " * "
     220           0 :         << xnorm << '\n';
     221           0 :     status = MooseConvergenceStatus::CONVERGED;
     222             :   }
     223      463251 :   else if (_nl_rel_div_tol > 0 && fnorm > ref_residual * _nl_rel_div_tol)
     224             :   {
     225           2 :     oss << "Diverged due to relative residual " << ref_residual << " > divergence tolerance "
     226           2 :         << _nl_rel_div_tol << " * relative residual " << ref_residual << '\n';
     227           2 :     status = MooseConvergenceStatus::DIVERGED;
     228             :   }
     229      463249 :   else if (_nl_abs_div_tol > 0 && fnorm > _nl_abs_div_tol)
     230             :   {
     231           9 :     oss << "Diverged due to residual " << fnorm << " > absolute divergence tolerance "
     232           9 :         << _nl_abs_div_tol << '\n';
     233           9 :     status = MooseConvergenceStatus::DIVERGED;
     234             :   }
     235      463240 :   else if (_nl_current_pingpong > _nl_max_pingpong)
     236             :   {
     237           9 :     oss << "Diverged due to maximum nonlinear residual pingpong achieved" << '\n';
     238           9 :     status = MooseConvergenceStatus::DIVERGED;
     239             :   }
     240             : 
     241      750624 :   system._last_nl_rnorm = fnorm;
     242      750624 :   system._current_nl_its = static_cast<unsigned int>(n_iter);
     243             : 
     244      750624 :   std::string msg;
     245      750624 :   msg = oss.str();
     246      750624 :   if (_app.multiAppLevel() > 0)
     247      383750 :     MooseUtils::indentMessage(_app.name(), msg);
     248      750624 :   if (msg.length() > 0)
     249             : #if !PETSC_VERSION_LESS_THAN(3, 17, 0)
     250      287393 :     LibmeshPetscCallA(_fe_problem.comm().get(), PetscInfo(snes, "%s", msg.c_str()));
     251             : #else
     252             :     LibmeshPetscCallA(_fe_problem.comm().get(), PetscInfo(snes, msg.c_str()));
     253             : #endif
     254             : 
     255      750624 :   verboseOutput(oss);
     256             : 
     257      750624 :   return status;
     258      750624 : }

Generated by: LCOV version 1.14