LCOV - code coverage report
Current view: top level - src/convergence - ReferenceResidualConvergence.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #33416 (b10b36) with base 9fbd27 Lines: 219 231 94.8 %
Date: 2026-07-23 16:15:30 Functions: 8 8 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 "ReferenceResidualConvergence.h"
      12             : #include "ReferenceResidualProblem.h"
      13             : #include "FEProblemBase.h"
      14             : #include "PetscSupport.h"
      15             : #include "Executioner.h"
      16             : #include "NonlinearSystemBase.h"
      17             : #include "TaggingInterface.h"
      18             : #include "AuxiliarySystem.h"
      19             : #include "MooseVariableScalar.h"
      20             : #include "NonlinearSystem.h"
      21             : 
      22             : // PETSc includes
      23             : #include <petscsnes.h>
      24             : 
      25             : registerMooseObject("MooseApp", ReferenceResidualConvergence);
      26             : 
      27             : InputParameters
      28        4100 : ReferenceResidualConvergence::validParams()
      29             : {
      30        4100 :   InputParameters params = DefaultNonlinearConvergence::validParams();
      31        4100 :   params += ReferenceResidualInterface::validParams();
      32             : 
      33        4100 :   params.addClassDescription(
      34             :       "Check the convergence of a problem with respect to a user-supplied reference solution."
      35             :       " Replaces ReferenceResidualProblem, currently still used in conjunction with it.");
      36             : 
      37        4100 :   return params;
      38           0 : }
      39             : 
      40         537 : ReferenceResidualConvergence::ReferenceResidualConvergence(const InputParameters & parameters)
      41             :   : DefaultNonlinearConvergence(parameters),
      42             :     ReferenceResidualInterface(this),
      43         537 :     _norm_type_enum(getParam<MooseEnum>("normalization_type")),
      44        1074 :     _accept_mult(getParam<Real>("acceptable_multiplier")),
      45        1074 :     _accept_iters(getParam<unsigned int>("acceptable_iterations")),
      46        1074 :     _nl_sys_num(_fe_problem.solverSysNum(getParam<SolverSystemName>("solver_sys"))),
      47         537 :     _residual_vector(nullptr),
      48         537 :     _reference_vector(nullptr),
      49         537 :     _zero_ref_type(
      50        1074 :         getParam<MooseEnum>("zero_reference_residual_treatment").getEnum<ZeroReferenceType>()),
      51        1074 :     _unscale_the_residual(getParam<bool>("unscale_the_residual")),
      52        1611 :     _reference_vector_tag_id(Moose::INVALID_TAG_ID)
      53             : {
      54         657 :   if (_fe_problem.numNonlinearSystems() > 1 && !isParamSetByUser("solver_sys"))
      55           0 :     paramError("solver_sys",
      56             :                "Reference residual problem does not currently support multiple nonlinear systems "
      57             :                "in a single Convergence object. Multiple Convergence objects can be used, one for "
      58             :                "each nonlinear system, via the 'solver_sys' parameter.");
      59             : 
      60        1074 :   if (parameters.isParamValid("residual_vector"))
      61             :   {
      62             :     const auto residual_vector_tag_id =
      63          52 :         _fe_problem.getVectorTagID(getParam<TagName>("residual_vector"));
      64          26 :     _residual_vector =
      65          26 :         &_fe_problem.getNonlinearSystemBase(_nl_sys_num).getVector(residual_vector_tag_id);
      66             :   }
      67             :   else
      68         511 :     _residual_vector = &_fe_problem.getNonlinearSystemBase(_nl_sys_num).RHS();
      69             : 
      70        1074 :   if (parameters.isParamValid("reference_vector"))
      71             :   {
      72         994 :     _reference_vector_tag_id = _fe_problem.getVectorTagID(getParam<TagName>("reference_vector"));
      73         491 :     _reference_vector =
      74         491 :         &_fe_problem.getNonlinearSystemBase(_nl_sys_num).getVector(_reference_vector_tag_id);
      75             :   }
      76             :   else
      77          40 :     mooseDeprecated(
      78             :         "No `reference_vector` is provided, thus the Reference Residual convergence method will "
      79             :         "revert to default tolerance checking. `reference_vector` will become a required parameter "
      80             :         "on June 1st, 2027. If you are using `ReferenceResidualProblem`, either provide a "
      81             :         "reference_vector or use a standard problem type (e.g., remove "
      82             :         "Problem/type=ReferenceResidualProblem from your input file). If you are using "
      83             :         "`ReferenceResidualConvergence`, either provide a reference_vector or utilize "
      84             :         "`DefaultNonlinearConvergence` instead.");
      85             : 
      86         531 :   if (_norm_type_enum == "LOCAL_L2")
      87             :   {
      88          82 :     _norm_type = libMesh::DISCRETE_L2;
      89          82 :     _local_norm = true;
      90             :   }
      91         449 :   else if (_norm_type_enum == "GLOBAL_L2")
      92             :   {
      93         397 :     _norm_type = libMesh::DISCRETE_L2;
      94         397 :     _local_norm = false;
      95             :   }
      96          52 :   else if (_norm_type_enum == "LOCAL_LINF")
      97             :   {
      98          26 :     _norm_type = libMesh::DISCRETE_L_INF;
      99          26 :     _local_norm = true;
     100             :   }
     101          26 :   else if (_norm_type_enum == "GLOBAL_LINF")
     102             :   {
     103          26 :     _norm_type = libMesh::DISCRETE_L_INF;
     104          26 :     _local_norm = false;
     105             :   }
     106             :   else
     107             :     mooseAssert(false, "This point should not be reached.");
     108             : 
     109         747 :   if (_local_norm && !parameters.isParamValid("reference_vector"))
     110          12 :     paramError("reference_vector", "If local norm is used, a reference_vector must be provided.");
     111         525 : }
     112             : 
     113             : NonlinearSystemBase &
     114       36599 : ReferenceResidualConvergence::nonlinearSystem()
     115             : {
     116       36599 :   return _fe_problem.getNonlinearSystemBase(_nl_sys_num);
     117             : }
     118             : 
     119             : void
     120         507 : ReferenceResidualConvergence::initialSetup()
     121             : {
     122         507 :   DefaultNonlinearConvergence::initialSetup();
     123             :   // If no refernce_vector is provided, just revert to DefaultNonlinearConvergence behavior
     124         507 :   if (!_reference_vector)
     125          26 :     return;
     126             : 
     127         481 :   auto & nonlinear_sys = nonlinearSystem();
     128         481 :   auto & s = nonlinear_sys.system();
     129             : 
     130             :   // If the user provides reference_vector, that implies that they want the
     131             :   // individual variables compared against their reference quantities in the
     132             :   // tag vector. The code depends on having _soln_var_names populated,
     133             :   // so fill that out if they didn't specify solution_variables.
     134        1700 :   for (const auto var_num : make_range(s.n_vars()))
     135        1219 :     _soln_var_names.push_back(s.variable_name(var_num));
     136         481 :   const auto n_soln_vars = nonlinear_sys.nVariables();
     137             : 
     138         962 :   const auto converge_on = getParam<std::vector<NonlinearVariableName>>("converge_on");
     139         481 :   if (!converge_on.empty())
     140             :   {
     141         146 :     _converge_on_var.assign(n_soln_vars, false);
     142         728 :     for (std::size_t i = 0; i < n_soln_vars; ++i)
     143        1578 :       for (const auto & c : converge_on)
     144        1432 :         if (MooseUtils::globCompare(_soln_var_names[i], c))
     145             :         {
     146         436 :           _converge_on_var[i] = true;
     147         436 :           break;
     148             :         }
     149             :   }
     150             :   else
     151         335 :     _converge_on_var.assign(n_soln_vars, true);
     152             : 
     153         481 :   unsigned int num_variables_in_groups = 0;
     154         587 :   for (const auto i : index_range(_group_variables))
     155             :   {
     156         106 :     num_variables_in_groups += _group_variables[i].size();
     157         106 :     if (_group_variables[i].size() == 1)
     158           0 :       paramError("group_variables",
     159             :                  "variable ",
     160           0 :                  _group_variables[i][0],
     161             :                  " is not grouped with other variables.");
     162             :   }
     163             : 
     164             :   // If no groups, size = n_soln_vars
     165         481 :   unsigned int n_groups = n_soln_vars - num_variables_in_groups + _group_variables.size();
     166         481 :   _group_ref_resid.resize(n_groups);
     167         481 :   _group_resid.resize(n_groups);
     168         481 :   _group_names.resize(n_groups);
     169         481 :   _converge_on_group.assign(n_groups, true);
     170         481 :   _scaling_factors.resize(n_soln_vars);
     171             : 
     172             :   // Check to make sure variables aren't in multiple groups
     173         481 :   if (_use_group_variables)
     174             :   {
     175          56 :     std::set<std::string> check_duplicate;
     176         162 :     for (const auto i : index_range(_group_variables))
     177         368 :       for (const auto j : index_range(_group_variables[i]))
     178         262 :         check_duplicate.insert(_group_variables[i][j]);
     179             : 
     180          56 :     if (check_duplicate.size() != num_variables_in_groups)
     181           0 :       paramError("group_variables", "A variable cannot be included in multiple groups.");
     182          56 :   }
     183             : 
     184         481 :   _soln_vars.clear();
     185        1700 :   for (const auto i : make_range(n_soln_vars))
     186             :   {
     187        1219 :     bool found_match = false;
     188        2737 :     for (const auto var_num : make_range(s.n_vars()))
     189        2737 :       if (_soln_var_names[i] == s.variable_name(var_num))
     190             :       {
     191        1219 :         _soln_vars.push_back(var_num);
     192        1219 :         found_match = true;
     193        1219 :         break;
     194             :       }
     195             : 
     196        1219 :     if (!found_match)
     197           0 :       mooseError("Could not find solution variable '",
     198           0 :                  _soln_var_names[i],
     199             :                  "' in system '",
     200           0 :                  s.name(),
     201             :                  "'.");
     202             :   }
     203             : 
     204         481 :   unsigned int ungroup_index = 0;
     205         481 :   if (_use_group_variables)
     206          56 :     ungroup_index = _group_variables.size();
     207             : 
     208             :   // Determine which group each variable belongs to
     209         481 :   _group_index.resize(n_soln_vars);
     210         481 :   _is_var_grouped.assign(n_soln_vars, false);
     211        1688 :   for (const auto i : index_range(_soln_vars))
     212             :   {
     213        1213 :     if (_use_group_variables)
     214             :     {
     215         476 :       for (const auto j : index_range(_group_variables))
     216         436 :         if (std::find(_group_variables[j].begin(),
     217         436 :                       _group_variables[j].end(),
     218         872 :                       s.variable_name(_soln_vars[i])) != _group_variables[j].end())
     219             :         {
     220         256 :           if (!_converge_on_var[i])
     221          12 :             paramError("converge_on",
     222             :                        "You added variable '",
     223           6 :                        _soln_var_names[i],
     224             :                        "' to a group but excluded it from the convergence check. This is not "
     225             :                        "permitted.");
     226             : 
     227         250 :           _group_index[i] = j;
     228         250 :           _is_var_grouped[i] = true;
     229         250 :           break;
     230             :         }
     231             : 
     232         290 :       if (!_is_var_grouped[i])
     233             :       {
     234          40 :         _group_index[i] = ungroup_index;
     235          40 :         ungroup_index++;
     236             :       }
     237             :     }
     238             :     else
     239         917 :       _group_index[i] = i;
     240             :   }
     241             : 
     242             :   // Check for variable groups containing both field and scalar variables
     243         575 :   for (const auto i : index_range(_group_variables))
     244             :   {
     245         100 :     unsigned int num_scalar_vars = 0;
     246         100 :     unsigned int num_field_vars = 0;
     247         100 :     if (_group_variables[i].size() > 1)
     248             :     {
     249         350 :       for (const auto j : index_range(_group_variables[i]))
     250         770 :         for (const auto var_num : make_range(s.n_vars()))
     251         770 :           if (_group_variables[i][j] == s.variable_name(var_num))
     252             :           {
     253         250 :             if (nonlinear_sys.isScalarVariable(_soln_vars[var_num]))
     254           0 :               ++num_scalar_vars;
     255             :             else
     256         250 :               ++num_field_vars;
     257         250 :             break;
     258             :           }
     259             :     }
     260         100 :     if (num_scalar_vars > 0 && num_field_vars > 0)
     261           0 :       paramWarning("group_variables",
     262             :                    "standard variables and scalar variables are grouped together in group ",
     263             :                    i);
     264             :   }
     265             : 
     266        1532 :   for (const auto i : index_range(_group_names))
     267             :   {
     268             :     // Accumulate names for a given group
     269        1057 :     std::vector<NonlinearVariableName> names;
     270        4418 :     for (const auto j : index_range(_group_index))
     271        3361 :       if (_group_index[j] == i)
     272             :       {
     273        1207 :         names.push_back(_soln_var_names[j]);
     274        1207 :         _converge_on_group[i] = _converge_on_group[i] && _converge_on_var[j];
     275             :       }
     276        1057 :     if (names.size() == 0)
     277           0 :       mooseError("Internal error, something is wrong with variable grouping");
     278        1057 :     else if (names.size() == 1)
     279         957 :       _group_names[i] = names[0];
     280             :     else
     281             :     {
     282         100 :       _group_names[i] = "(";
     283         350 :       for (const auto j : index_range(names))
     284             :       {
     285         250 :         _group_names[i] += names[j];
     286         250 :         if (j != names.size() - 1)
     287         150 :           _group_names[i] += ", ";
     288             :       }
     289         100 :       _group_names[i] += ")";
     290             :     }
     291        1057 :   }
     292         475 : }
     293             : 
     294             : void
     295       17815 : ReferenceResidualConvergence::updateReferenceResidual()
     296             : {
     297             :   // If no reference_vector is provided, this method is completely skipped
     298             : 
     299       17815 :   auto & nonlinear_sys = nonlinearSystem();
     300       17815 :   auto & s = nonlinear_sys.system();
     301             : 
     302       54312 :   for (const auto i : index_range(_scaling_factors))
     303       36497 :     if (nonlinear_sys.isScalarVariable(_soln_vars[i]))
     304           0 :       _scaling_factors[i] = nonlinear_sys.getScalarVariable(0, _soln_vars[i]).scalingFactor();
     305             :     else
     306       36497 :       _scaling_factors[i] = nonlinear_sys.getVariable(/*tid*/ 0, _soln_vars[i]).scalingFactor();
     307             : 
     308       17815 :   std::fill(_group_resid.begin(), _group_resid.end(), 0.0);
     309       17815 :   std::fill(_group_ref_resid.begin(), _group_ref_resid.end(), 0.0);
     310             : 
     311       54312 :   for (const auto i : index_range(_soln_vars))
     312             :   {
     313       36497 :     if (_converge_on_var[i])
     314             :     {
     315       35921 :       const auto group = _group_index[i];
     316             : 
     317             :       // Prepare residual
     318       35921 :       auto resid = Utility::pow<2>(s.calculate_norm(*_residual_vector, _soln_vars[i], _norm_type));
     319       35921 :       if (_unscale_the_residual)
     320             :       {
     321             :         mooseAssert(_scaling_factors[i], "Scaling factor must not be zero");
     322         288 :         resid /= Utility::pow<2>(_scaling_factors[i]);
     323             :       }
     324       35921 :       _group_resid[group] += resid;
     325             : 
     326             :       // Prepare reference residual. If local norm, this is actually the ratio of the residual
     327             :       // dividied by the reference at all DOF
     328             :       Real ref_resid;
     329       35921 :       if (_local_norm)
     330             :       {
     331             :         mooseAssert((*_residual_vector).size() == (*_reference_vector).size(),
     332             :                     "Sizes of nonlinear RHS and reference vector should be the same.");
     333             :         mooseAssert((*_reference_vector).size(), "Reference vector must be provided.");
     334        2754 :         auto ref = _reference_vector->clone();
     335             :         // Add a tiny number to the reference to prevent a divide by zero.
     336        2754 :         ref->add(std::numeric_limits<Number>::min());
     337        2754 :         auto div = (*_residual_vector).clone();
     338        2754 :         *div /= *ref;
     339        2754 :         ref_resid = Utility::pow<2>(s.calculate_norm(*div, _soln_vars[i], _norm_type));
     340        2754 :       }
     341             :       else
     342             :       {
     343             :         ref_resid =
     344       33167 :             Utility::pow<2>(s.calculate_norm(*_reference_vector, _soln_vars[i], _norm_type));
     345       33167 :         if (_unscale_the_residual)
     346         216 :           ref_resid /= Utility::pow<2>(_scaling_factors[i]);
     347             :       }
     348       35921 :       _group_ref_resid[group] += ref_resid;
     349             :     }
     350             :   }
     351             : 
     352       53691 :   for (const auto i : index_range(_group_resid))
     353             :   {
     354       35876 :     _group_resid[i] = std::sqrt(_group_resid[i]);
     355       35876 :     _group_ref_resid[i] = std::sqrt(_group_ref_resid[i]);
     356             :   }
     357       17815 : }
     358             : 
     359             : void
     360       18303 : ReferenceResidualConvergence::nonlinearConvergenceSetup()
     361             : {
     362             :   // If no refernce_vector is provided, just revert to DefaultNonlinearConvergence behavior
     363       18303 :   if (!_reference_vector)
     364         488 :     return;
     365             : 
     366       17815 :   updateReferenceResidual();
     367             : 
     368       17815 :   std::ostringstream out;
     369       17815 :   out << _name << ": " << _norm_type_enum << " Reference Residual check\n";
     370             : 
     371       17815 :   if (_group_names.size() > 0)
     372             :   {
     373             :     // Set residual and references so that they always have a spacing of 8
     374       17815 :     out << std::setprecision(2) << std::scientific;
     375       17815 :     unsigned int var_space = 0;
     376       53691 :     for (const auto i : index_range(_group_names))
     377       35876 :       if (_group_names[i].size() > var_space)
     378       17896 :         var_space = _group_names[i].size();
     379             : 
     380       53691 :     for (const auto i : index_range(_group_names))
     381             :     {
     382       35876 :       if (_converge_on_group[i])
     383             :       {
     384             :         // Print residual
     385       70600 :         out << "   " << std::setw(var_space + 8) << std::right << _group_names[i] + "-> res: "
     386       35300 :             << (_group_resid[i] < _abs_tol ? COLOR_YELLOW : COLOR_DEFAULT) << std::setw(8)
     387       35300 :             << _group_resid[i] << COLOR_DEFAULT;
     388             : 
     389             :         // Print res/ref ratio
     390       35300 :         if (_local_norm)
     391             :           out << "  local res/ref: "
     392        5508 :               << (_group_resid[i] / _group_ref_resid[i] < _rel_tol ? COLOR_GREEN : COLOR_DEFAULT)
     393        8262 :               << std::setw(8) << _group_ref_resid[i] << COLOR_DEFAULT << "\n";
     394             :         else
     395             :         {
     396             :           // Print reference first if not local norm
     397       32546 :           out << "  ref: " << std::setw(8) << _group_ref_resid[i] << "  res/ref: ";
     398             : 
     399       32546 :           if (!_group_ref_resid[i])
     400       28103 :             out << _group_resid[i] << "\n";
     401             :           else
     402        8886 :             out << (_group_resid[i] / _group_ref_resid[i] < _rel_tol ? COLOR_GREEN : COLOR_DEFAULT)
     403        8886 :                 << std::setw(8) << _group_resid[i] / _group_ref_resid[i] << COLOR_DEFAULT << "\n";
     404             :         }
     405             :       }
     406             :     }
     407       17815 :     _console << out.str() << std::flush;
     408             :   }
     409       17815 : }
     410             : 
     411             : bool
     412       33563 : ReferenceResidualConvergence::checkConvergenceIndividVars(
     413             :     const Real /*fnorm*/,
     414             :     const Real abs_tol,
     415             :     const Real rel_tol,
     416             :     const Real /*initial_residual_before_preset_bcs*/)
     417             : {
     418             :   // Convergence is checked via:
     419             :   // 1) Ratio of group residual to reference is less than relative tolerance
     420             :   // 2) if group residual is less than absolute tolerance
     421             :   // 3) if group reference residual is zero and:
     422             :   //   3.1) Convergence type is ZERO_TOLERANCE and group residual is zero (rare, but possible, and
     423             :   //        historically implemented that way)
     424             :   //   3.2) Convergence type is RELATIVE_TOLERANCE and group residual
     425             :   //        is less than relative tolerance. (i.e., using the relative tolerance to check group
     426             :   //        convergence in an absolute way)
     427             : 
     428       33563 :   bool convergedRelative = true;
     429      101466 :   for (const auto i : index_range(_group_resid))
     430       67903 :     convergedRelative &=
     431       62689 :         ((!_local_norm && _group_resid[i] < _group_ref_resid[i] * rel_tol) ||
     432      190240 :          (_local_norm && _group_ref_resid[i] < rel_tol) || _group_resid[i] < abs_tol ||
     433       59648 :          (!_group_ref_resid[i] && !_local_norm &&
     434       51918 :           ((_zero_ref_type == ZeroReferenceType::ZERO_TOLERANCE && !_group_resid[i]) ||
     435       51918 :            (_zero_ref_type == ZeroReferenceType::RELATIVE_TOLERANCE &&
     436        1970 :             _group_resid[i] <= rel_tol))));
     437       33563 :   return convergedRelative;
     438             : }
     439             : 
     440             : bool
     441       18145 : ReferenceResidualConvergence::checkResidualConvergence(const unsigned int n_iter,
     442             :                                                        const Real fnorm,
     443             :                                                        const Real ref_norm,
     444             :                                                        const Real rel_tol,
     445             :                                                        const Real abs_tol,
     446             :                                                        std::ostringstream & oss)
     447             : {
     448             :   // If no refernce_vector is provided, just revert to DefaultNonlinearConvergence behavior
     449       18145 :   if (!_reference_vector)
     450         488 :     return DefaultNonlinearConvergence::checkResidualConvergence(
     451         488 :         n_iter, fnorm, ref_norm, rel_tol, abs_tol, oss);
     452             : 
     453       17657 :   if (checkConvergenceIndividVars(fnorm, abs_tol, rel_tol, ref_norm))
     454             :   {
     455        1697 :     oss << "Converged normally";
     456        1697 :     return true;
     457             :   }
     458       31866 :   else if (n_iter >= _accept_iters &&
     459       15906 :            checkConvergenceIndividVars(
     460       15906 :                fnorm, abs_tol * _accept_mult, rel_tol * _accept_mult, ref_norm))
     461             :   {
     462             :     oss << "  Converged due a larger acceptable tolerance due to `acceptible_multiplier` after "
     463          36 :            "`acceptible_iterations`.";
     464          36 :     _console << "  Converged due to ACCEPTABLE tolerances" << std::endl;
     465          36 :     return true;
     466             :   }
     467             : 
     468       15924 :   return false;
     469             : }

Generated by: LCOV version 1.14