https://mooseframework.inl.gov
ReferenceResidualConvergence.C
Go to the documentation of this file.
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
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 
26 
29 {
32 
33  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  return params;
38 }
39 
41  : DefaultNonlinearConvergence(parameters),
43  _norm_type_enum(getParam<MooseEnum>("normalization_type")),
44  _accept_mult(getParam<Real>("acceptable_multiplier")),
45  _accept_iters(getParam<unsigned int>("acceptable_iterations")),
46  _residual_vector(nullptr),
47  _reference_vector(nullptr),
48  _zero_ref_type(
49  getParam<MooseEnum>("zero_reference_residual_treatment").getEnum<ZeroReferenceType>()),
50  _unscale_the_residual(getParam<bool>("unscale_the_residual")),
51  _reference_vector_tag_id(Moose::INVALID_TAG_ID)
52 {
53  if (_fe_problem.numNonlinearSystems() > 1 && !isParamSetByUser("solver_sys"))
54  paramError("solver_sys",
55  "Reference residual problem does not currently support multiple nonlinear systems "
56  "in a single Convergence object. Multiple Convergence objects can be used, one for "
57  "each nonlinear system, via the 'solver_sys' parameter.");
58 
59  const auto solver_sys = getParam<SolverSystemName>("solver_sys");
60  const auto num = _fe_problem.solverSysNum(solver_sys);
61  if (parameters.isParamValid("residual_vector"))
62  {
63  const auto residual_vector_tag_id =
64  _fe_problem.getVectorTagID(getParam<TagName>("residual_vector"));
65  _residual_vector = &_fe_problem.getNonlinearSystemBase(num).getVector(residual_vector_tag_id);
66  }
67  else
69 
70  if (parameters.isParamValid("reference_vector"))
71  {
72  _reference_vector_tag_id = _fe_problem.getVectorTagID(getParam<TagName>("reference_vector"));
75  }
76  else
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  if (_norm_type_enum == "LOCAL_L2")
87  {
89  _local_norm = true;
90  }
91  else if (_norm_type_enum == "GLOBAL_L2")
92  {
94  _local_norm = false;
95  }
96  else if (_norm_type_enum == "LOCAL_LINF")
97  {
99  _local_norm = true;
100  }
101  else if (_norm_type_enum == "GLOBAL_LINF")
102  {
104  _local_norm = false;
105  }
106  else
107  mooseAssert(false, "This point should not be reached.");
108 
109  if (_local_norm && !parameters.isParamValid("reference_vector"))
110  paramError("reference_vector", "If local norm is used, a reference_vector must be provided.");
111 }
112 
113 void
115 {
117  // If no refernce_vector is provided, just revert to DefaultNonlinearConvergence behavior
118  if (!_reference_vector)
119  return;
120 
121  const auto solver_sys = getParam<SolverSystemName>("solver_sys");
122  const auto num = _fe_problem.solverSysNum(solver_sys);
123 
124  auto & nonlinear_sys = _fe_problem.getNonlinearSystemBase(num);
125  auto & s = nonlinear_sys.system();
126 
127  // If the user provides reference_vector, that implies that they want the
128  // individual variables compared against their reference quantities in the
129  // tag vector. The code depends on having _soln_var_names populated,
130  // so fill that out if they didn't specify solution_variables.
131  for (const auto var_num : make_range(s.n_vars()))
132  _soln_var_names.push_back(s.variable_name(var_num));
133  const auto n_soln_vars = nonlinear_sys.nVariables();
134 
135  const auto converge_on = getParam<std::vector<NonlinearVariableName>>("converge_on");
136  if (!converge_on.empty())
137  {
138  _converge_on_var.assign(n_soln_vars, false);
139  for (std::size_t i = 0; i < n_soln_vars; ++i)
140  for (const auto & c : converge_on)
142  {
143  _converge_on_var[i] = true;
144  break;
145  }
146  }
147  else
148  _converge_on_var.assign(n_soln_vars, true);
149 
150  unsigned int num_variables_in_groups = 0;
151  for (const auto i : index_range(_group_variables))
152  {
153  num_variables_in_groups += _group_variables[i].size();
154  if (_group_variables[i].size() == 1)
155  paramError("group_variables",
156  "variable ",
157  _group_variables[i][0],
158  " is not grouped with other variables.");
159  }
160 
161  // If no groups, size = n_soln_vars
162  unsigned int n_groups = n_soln_vars - num_variables_in_groups + _group_variables.size();
163  _group_ref_resid.resize(n_groups);
164  _group_resid.resize(n_groups);
165  _group_names.resize(n_groups);
166  _converge_on_group.assign(n_groups, true);
167  _scaling_factors.resize(n_soln_vars);
168 
169  // Check to make sure variables aren't in multiple groups
171  {
172  std::set<std::string> check_duplicate;
173  for (const auto i : index_range(_group_variables))
174  for (const auto j : index_range(_group_variables[i]))
175  check_duplicate.insert(_group_variables[i][j]);
176 
177  if (check_duplicate.size() != num_variables_in_groups)
178  paramError("group_variables", "A variable cannot be included in multiple groups.");
179  }
180 
181  _soln_vars.clear();
182  for (const auto i : make_range(n_soln_vars))
183  {
184  bool found_match = false;
185  for (const auto var_num : make_range(s.n_vars()))
186  if (_soln_var_names[i] == s.variable_name(var_num))
187  {
188  _soln_vars.push_back(var_num);
189  found_match = true;
190  break;
191  }
192 
193  if (!found_match)
194  mooseError("Could not find solution variable '",
195  _soln_var_names[i],
196  "' in system '",
197  s.name(),
198  "'.");
199  }
200 
201  unsigned int ungroup_index = 0;
203  ungroup_index = _group_variables.size();
204 
205  // Determine which group each variable belongs to
206  _group_index.resize(n_soln_vars);
207  _is_var_grouped.assign(n_soln_vars, false);
208  for (const auto i : index_range(_soln_vars))
209  {
211  {
212  for (const auto j : index_range(_group_variables))
213  if (std::find(_group_variables[j].begin(),
214  _group_variables[j].end(),
215  s.variable_name(_soln_vars[i])) != _group_variables[j].end())
216  {
217  if (!_converge_on_var[i])
218  paramError("converge_on",
219  "You added variable '",
220  _soln_var_names[i],
221  "' to a group but excluded it from the convergence check. This is not "
222  "permitted.");
223 
224  _group_index[i] = j;
225  _is_var_grouped[i] = true;
226  break;
227  }
228 
229  if (!_is_var_grouped[i])
230  {
231  _group_index[i] = ungroup_index;
232  ungroup_index++;
233  }
234  }
235  else
236  _group_index[i] = i;
237  }
238 
239  // Check for variable groups containing both field and scalar variables
240  for (const auto i : index_range(_group_variables))
241  {
242  unsigned int num_scalar_vars = 0;
243  unsigned int num_field_vars = 0;
244  if (_group_variables[i].size() > 1)
245  {
246  for (const auto j : index_range(_group_variables[i]))
247  for (const auto var_num : make_range(s.n_vars()))
248  if (_group_variables[i][j] == s.variable_name(var_num))
249  {
250  if (nonlinear_sys.isScalarVariable(_soln_vars[var_num]))
251  ++num_scalar_vars;
252  else
253  ++num_field_vars;
254  break;
255  }
256  }
257  if (num_scalar_vars > 0 && num_field_vars > 0)
258  paramWarning("group_variables",
259  "standard variables and scalar variables are grouped together in group ",
260  i);
261  }
262 
263  for (const auto i : index_range(_group_names))
264  {
265  // Accumulate names for a given group
266  std::vector<NonlinearVariableName> names;
267  for (const auto j : index_range(_group_index))
268  if (_group_index[j] == i)
269  {
270  names.push_back(_soln_var_names[j]);
272  }
273  if (names.size() == 0)
274  mooseError("Internal error, something is wrong with variable grouping");
275  else if (names.size() == 1)
276  _group_names[i] = names[0];
277  else
278  {
279  _group_names[i] = "(";
280  for (const auto j : index_range(names))
281  {
282  _group_names[i] += names[j];
283  if (j != names.size() - 1)
284  _group_names[i] += ", ";
285  }
286  _group_names[i] += ")";
287  }
288  }
289 }
290 
291 void
293 {
294  // If no reference_vector is provided, this method is completely skipped
295 
296  auto & current_nl_sys = _fe_problem.currentNonlinearSystem();
297  auto & s = current_nl_sys.system();
298 
299  for (const auto i : index_range(_scaling_factors))
300  if (current_nl_sys.isScalarVariable(_soln_vars[i]))
301  _scaling_factors[i] = current_nl_sys.getScalarVariable(0, _soln_vars[i]).scalingFactor();
302  else
303  _scaling_factors[i] = current_nl_sys.getVariable(/*tid*/ 0, _soln_vars[i]).scalingFactor();
304 
305  std::fill(_group_resid.begin(), _group_resid.end(), 0.0);
306  std::fill(_group_ref_resid.begin(), _group_ref_resid.end(), 0.0);
307 
308  for (const auto i : index_range(_soln_vars))
309  {
310  if (_converge_on_var[i])
311  {
312  const auto group = _group_index[i];
313 
314  // Prepare residual
315  auto resid = Utility::pow<2>(s.calculate_norm(*_residual_vector, _soln_vars[i], _norm_type));
317  {
318  mooseAssert(_scaling_factors[i], "Scaling factor must not be zero");
319  resid /= Utility::pow<2>(_scaling_factors[i]);
320  }
321  _group_resid[group] += resid;
322 
323  // Prepare reference residual. If local norm, this is actually the ratio of the residual
324  // dividied by the reference at all DOF
325  Real ref_resid;
326  if (_local_norm)
327  {
328  mooseAssert((*_residual_vector).size() == (*_reference_vector).size(),
329  "Sizes of nonlinear RHS and reference vector should be the same.");
330  mooseAssert((*_reference_vector).size(), "Reference vector must be provided.");
331  auto ref = _reference_vector->clone();
332  // Add a tiny number to the reference to prevent a divide by zero.
334  auto div = (*_residual_vector).clone();
335  *div /= *ref;
336  ref_resid = Utility::pow<2>(s.calculate_norm(*div, _soln_vars[i], _norm_type));
337  }
338  else
339  {
340  ref_resid =
341  Utility::pow<2>(s.calculate_norm(*_reference_vector, _soln_vars[i], _norm_type));
343  ref_resid /= Utility::pow<2>(_scaling_factors[i]);
344  }
345  _group_ref_resid[group] += ref_resid;
346  }
347  }
348 
349  for (const auto i : index_range(_group_resid))
350  {
353  }
354 }
355 
356 void
358 {
359  // If no refernce_vector is provided, just revert to DefaultNonlinearConvergence behavior
360  if (!_reference_vector)
361  return;
362 
364 
365  std::ostringstream out;
366  out << _name << ": " << _norm_type_enum << " Reference Residual check\n";
367 
368  if (_group_names.size() > 0)
369  {
370  // Set residual and references so that they always have a spacing of 8
371  out << std::setprecision(2) << std::scientific;
372  unsigned int var_space = 0;
373  for (const auto i : index_range(_group_names))
374  if (_group_names[i].size() > var_space)
375  var_space = _group_names[i].size();
376 
377  for (const auto i : index_range(_group_names))
378  {
379  if (_converge_on_group[i])
380  {
381  // Print residual
382  out << " " << std::setw(var_space + 8) << std::right << _group_names[i] + "-> res: "
383  << (_group_resid[i] < _abs_tol ? COLOR_YELLOW : COLOR_DEFAULT) << std::setw(8)
384  << _group_resid[i] << COLOR_DEFAULT;
385 
386  // Print res/ref ratio
387  if (_local_norm)
388  out << " local res/ref: "
389  << (_group_resid[i] / _group_ref_resid[i] < _rel_tol ? COLOR_GREEN : COLOR_DEFAULT)
390  << std::setw(8) << _group_ref_resid[i] << COLOR_DEFAULT << "\n";
391  else
392  {
393  // Print reference first if not local norm
394  out << " ref: " << std::setw(8) << _group_ref_resid[i] << " res/ref: ";
395 
396  if (!_group_ref_resid[i])
397  out << _group_resid[i] << "\n";
398  else
399  out << (_group_resid[i] / _group_ref_resid[i] < _rel_tol ? COLOR_GREEN : COLOR_DEFAULT)
400  << std::setw(8) << _group_resid[i] / _group_ref_resid[i] << COLOR_DEFAULT << "\n";
401  }
402  }
403  }
404  _console << out.str() << std::flush;
405  }
406 }
407 
408 bool
410  const Real /*fnorm*/,
411  const Real abs_tol,
412  const Real rel_tol,
413  const Real /*initial_residual_before_preset_bcs*/)
414 {
415  // Convergence is checked via:
416  // 1) Ratio of group residual to reference is less than relative tolerance
417  // 2) if group residual is less than absolute tolerance
418  // 3) if group reference residual is zero and:
419  // 3.1) Convergence type is ZERO_TOLERANCE and group residual is zero (rare, but possible, and
420  // historically implemented that way)
421  // 3.2) Convergence type is RELATIVE_TOLERANCE and group residual
422  // is less than relative tolerance. (i.e., using the relative tolerance to check group
423  // convergence in an absolute way)
424 
425  bool convergedRelative = true;
426  for (const auto i : index_range(_group_resid))
427  convergedRelative &=
428  ((!_local_norm && _group_resid[i] < _group_ref_resid[i] * rel_tol) ||
429  (_local_norm && _group_ref_resid[i] < rel_tol) || _group_resid[i] < abs_tol ||
430  (!_group_ref_resid[i] && !_local_norm &&
433  _group_resid[i] <= rel_tol))));
434  return convergedRelative;
435 }
436 
437 bool
439  const Real fnorm,
440  const Real ref_norm,
441  const Real rel_tol,
442  const Real abs_tol,
443  std::ostringstream & oss)
444 {
445  // If no refernce_vector is provided, just revert to DefaultNonlinearConvergence behavior
446  if (!_reference_vector)
448  it, fnorm, ref_norm, rel_tol, abs_tol, oss);
449 
450  if (checkConvergenceIndividVars(fnorm, abs_tol, rel_tol, ref_norm))
451  {
452  oss << "Converged normally";
453  return true;
454  }
455  else if (it >= _accept_iters &&
457  fnorm, abs_tol * _accept_mult, rel_tol * _accept_mult, ref_norm))
458  {
459  oss << " Converged due a larger acceptable tolerance due to `acceptible_multiplier` after "
460  "`acceptible_iterations`.";
461  _console << " Converged due to ACCEPTABLE tolerances" << std::endl;
462  return true;
463  }
464 
465  return false;
466 }
virtual TagID getVectorTagID(const TagName &tag_name) const
Get a TagID from a TagName.
Definition: SubProblem.C:204
virtual void nonlinearConvergenceSetup() override
Performs setup necessary for each call to checkConvergence.
KOKKOS_INLINE_FUNCTION const T * find(const T &target, const T *const begin, const T *const end)
Find a value in an array.
Definition: KokkosUtils.h:40
virtual bool checkResidualConvergence(const unsigned int it, const Real fnorm, const Real ref_norm, const Real rel_tol, const Real abs_tol, std::ostringstream &oss) override
Check the absolute and relative convergence of the nonlinear solution.
const std::string & _name
The name of this class.
Definition: MooseBase.h:391
Interface class shared between ReferenceResidualProblem and ReferenceResidualConvergence.
PetscReal _abs_tol
Nonlinear absolute tolerance.
virtual void initialSetup() override
Gets called at the beginning of the simulation before this object is asked to do its job...
void paramError(const std::string &param, Args... args) const
Emits an error prefixed with the file and line number of the given param (from the input file) along ...
Definition: MooseBase.h:467
virtual std::size_t numNonlinearSystems() const override
std::vector< NonlinearVariableName > _group_names
std::vector< NonlinearVariableName > _soln_var_names
std::vector< std::vector< NonlinearVariableName > > _group_variables
Name of variables that are grouped together to check convergence.
ReferenceResidualConvergence(const InputParameters &parameters)
const MooseEnum _norm_type_enum
Enum holding the normalization type.
const InputParameters & parameters() const
Get the parameters of the object.
Definition: MooseBase.h:131
std::vector< bool > _converge_on_var
Flag for each solution variable or group being in &#39;converge_on&#39;.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
virtual std::unique_ptr< NumericVector< Number > > clone() const =0
const NumericVector< Number > * _reference_vector
The vector storing the reference residual values.
void updateReferenceResidual()
Computes the reference residuals for each group.
static InputParameters validParams()
registerMooseObject("MooseApp", ReferenceResidualConvergence)
bool _use_group_variables
True if any variables are grouped.
std::vector< unsigned int > _soln_vars
TagID _reference_vector_tag_id
The reference vector tag id.
NonlinearSystemBase & currentNonlinearSystem()
const bool _unscale_the_residual
Bool to unscale the residual before convergence checks and screen output.
void mooseDeprecated(Args &&... args) const
This is a "smart" enum class intended to replace many of the shortcomings in the C++ enum type It sho...
Definition: MooseEnum.h:54
bool _local_norm
Flag to optionally perform normalization of residual by reference residual before or after L2 norm is...
NonlinearSystemBase & getNonlinearSystemBase(const unsigned int sys_num)
bool checkConvergenceIndividVars(const Real fnorm, const Real abs_tol, const Real rel_tol, const Real initial_residual_before_preset_bcs)
Check the convergence by comparing the norm of each variable&#39;s residual separately against its refere...
virtual void initialSetup() override
Gets called at the beginning of the simulation before this object is asked to do its job...
Uses a reference residual to define relative convergence criteria.
const NumericVector< Number > * _residual_vector
The optional vector storing the reference residual values.
virtual NumericVector< Number > & RHS()=0
libMesh::FEMNormType _norm_type
Container for normalization type.
static InputParameters validParams()
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
unsigned int solverSysNum(const SolverSystemName &solver_sys_name) const override
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sqrt(_arg)) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tanh
enum ReferenceResidualConvergence::ZeroReferenceType _zero_ref_type
const TagID INVALID_TAG_ID
Definition: MooseTypes.C:23
OStreamProxy out
bool globCompare(const std::string &candidate, const std::string &pattern, std::size_t c, std::size_t p)
Definition: MooseUtils.C:932
IntRange< T > make_range(T beg, T end)
void mooseError(Args &&... args) const
Emits an error prefixed with object name and type and optionally a file path to the top-level block p...
Definition: MooseBase.h:281
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
std::vector< unsigned int > _group_index
Group number index for each variable.
std::vector< bool > _is_var_grouped
Vector of bools to signify if variable is in a group.
MOOSE now contains C++17 code, so give a reasonable error message stating what the user can do to add...
const ConsoleStream _console
An instance of helper class to write streams to the Console objects.
std::vector< Real > _scaling_factors
Local storage for the scaling factors applied to each of the variables to apply to _ref_resid_vars...
void paramWarning(const std::string &param, Args... args) const
virtual bool checkResidualConvergence(const unsigned int iter, const Real fnorm, const Real ref_norm, const Real rel_tol, const Real abs_tol, std::ostringstream &oss)
Check the absolute and relative convergence of the nonlinear solution.
auto min(const L &left, const R &right)
virtual NumericVector< Number > & getVector(const std::string &name)
Get a raw NumericVector by name.
Definition: SystemBase.C:934
bool isParamSetByUser(const std::string &name) const
Test if the supplied parameter is set by a user, as opposed to not set or set to default.
Definition: MooseBase.h:215
Default nonlinear convergence criteria for FEProblem.
void ErrorVector unsigned int
auto index_range(const T &sizable)
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template pow< 2 >(tan(_arg))+1.0) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(sqrt
PetscReal _rel_tol
Nonlinear relative tolerance.
ZeroReferenceType
Container for convergence treatment when the reference residual is zero.
bool isParamValid(const std::string &name) const
This method returns parameters that have been initialized in one fashion or another, i.e.
virtual libMesh::System & system() override
Get the reference to the libMesh system.