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  // This restriction is primarily due to reference and residual vector parameters
55  paramError("nl_sys_names",
56  "reference residual problem does not currently support multiple nonlinear systems");
57 
58  if (parameters.isParamValid("residual_vector"))
59  {
60  const auto residual_vector_tag_id =
61  _fe_problem.getVectorTagID(getParam<TagName>("residual_vector"));
62  _residual_vector = &_fe_problem.getNonlinearSystemBase(0).getVector(residual_vector_tag_id);
63  }
64  else
66 
67  if (parameters.isParamValid("reference_vector"))
68  {
69  _reference_vector_tag_id = _fe_problem.getVectorTagID(getParam<TagName>("reference_vector"));
71  }
72  else
74  "No `reference_vector` is provided, thus the Reference Residual convergence method will "
75  "revert to default tolerance checking. `reference_vector` will become a required parameter "
76  "on June 1st, 2027. If you are using `ReferenceResidualProblem`, either provide a "
77  "reference_vector or use a standard problem type (e.g., remove "
78  "Problem/type=ReferenceResidualProblem from your input file). If you are using "
79  "`ReferenceResidualConvergence`, either provide a reference_vector or utilize "
80  "`DefaultNonlinearConvergence` instead.");
81 
82  if (_norm_type_enum == "LOCAL_L2")
83  {
85  _local_norm = true;
86  }
87  else if (_norm_type_enum == "GLOBAL_L2")
88  {
90  _local_norm = false;
91  }
92  else if (_norm_type_enum == "LOCAL_LINF")
93  {
95  _local_norm = true;
96  }
97  else if (_norm_type_enum == "GLOBAL_LINF")
98  {
100  _local_norm = false;
101  }
102  else
103  mooseAssert(false, "This point should not be reached.");
104 
105  if (_local_norm && !parameters.isParamValid("reference_vector"))
106  paramError("reference_vector", "If local norm is used, a reference_vector must be provided.");
107 }
108 
109 void
111 {
113  // If no refernce_vector is provided, just revert to DefaultNonlinearConvergence behavior
114  if (!_reference_vector)
115  return;
116 
117  auto & nonlinear_sys = _fe_problem.getNonlinearSystemBase(/*nl_sys=*/0);
118  auto & s = nonlinear_sys.system();
119 
120  // If the user provides reference_vector, that implies that they want the
121  // individual variables compared against their reference quantities in the
122  // tag vector. The code depends on having _soln_var_names populated,
123  // so fill that out if they didn't specify solution_variables.
124  for (const auto var_num : make_range(s.n_vars()))
125  _soln_var_names.push_back(s.variable_name(var_num));
126  const auto n_soln_vars = nonlinear_sys.nVariables();
127 
128  const auto converge_on = getParam<std::vector<NonlinearVariableName>>("converge_on");
129  if (!converge_on.empty())
130  {
131  _converge_on_var.assign(n_soln_vars, false);
132  for (std::size_t i = 0; i < n_soln_vars; ++i)
133  for (const auto & c : converge_on)
135  {
136  _converge_on_var[i] = true;
137  break;
138  }
139  }
140  else
141  _converge_on_var.assign(n_soln_vars, true);
142 
143  unsigned int num_variables_in_groups = 0;
144  for (const auto i : index_range(_group_variables))
145  {
146  num_variables_in_groups += _group_variables[i].size();
147  if (_group_variables[i].size() == 1)
148  paramError("group_variables",
149  "variable ",
150  _group_variables[i][0],
151  " is not grouped with other variables.");
152  }
153 
154  // If no groups, size = n_soln_vars
155  unsigned int n_groups = n_soln_vars - num_variables_in_groups + _group_variables.size();
156  _group_ref_resid.resize(n_groups);
157  _group_resid.resize(n_groups);
158  _group_names.resize(n_groups);
159  _converge_on_group.assign(n_groups, true);
160  _scaling_factors.resize(n_soln_vars);
161 
162  // Check to make sure variables aren't in multiple groups
164  {
165  std::set<std::string> check_duplicate;
166  for (const auto i : index_range(_group_variables))
167  for (const auto j : index_range(_group_variables[i]))
168  check_duplicate.insert(_group_variables[i][j]);
169 
170  if (check_duplicate.size() != num_variables_in_groups)
171  paramError("group_variables", "A variable cannot be included in multiple groups.");
172  }
173 
174  _soln_vars.clear();
175  for (const auto i : make_range(n_soln_vars))
176  {
177  bool found_match = false;
178  for (const auto var_num : make_range(s.n_vars()))
179  if (_soln_var_names[i] == s.variable_name(var_num))
180  {
181  _soln_vars.push_back(var_num);
182  found_match = true;
183  break;
184  }
185 
186  if (!found_match)
187  mooseError("Could not find solution variable '",
188  _soln_var_names[i],
189  "' in system '",
190  s.name(),
191  "'.");
192  }
193 
194  unsigned int ungroup_index = 0;
196  ungroup_index = _group_variables.size();
197 
198  // Determine which group each variable belongs to
199  _group_index.resize(n_soln_vars);
200  _is_var_grouped.assign(n_soln_vars, false);
201  for (const auto i : index_range(_soln_vars))
202  {
204  {
205  for (const auto j : index_range(_group_variables))
206  if (std::find(_group_variables[j].begin(),
207  _group_variables[j].end(),
208  s.variable_name(_soln_vars[i])) != _group_variables[j].end())
209  {
210  if (!_converge_on_var[i])
211  paramError("converge_on",
212  "You added variable '",
213  _soln_var_names[i],
214  "' to a group but excluded it from the convergence check. This is not "
215  "permitted.");
216 
217  _group_index[i] = j;
218  _is_var_grouped[i] = true;
219  break;
220  }
221 
222  if (!_is_var_grouped[i])
223  {
224  _group_index[i] = ungroup_index;
225  ungroup_index++;
226  }
227  }
228  else
229  _group_index[i] = i;
230  }
231 
232  // Check for variable groups containing both field and scalar variables
233  for (const auto i : index_range(_group_variables))
234  {
235  unsigned int num_scalar_vars = 0;
236  unsigned int num_field_vars = 0;
237  if (_group_variables[i].size() > 1)
238  {
239  for (const auto j : index_range(_group_variables[i]))
240  for (const auto var_num : make_range(s.n_vars()))
241  if (_group_variables[i][j] == s.variable_name(var_num))
242  {
243  if (nonlinear_sys.isScalarVariable(_soln_vars[var_num]))
244  ++num_scalar_vars;
245  else
246  ++num_field_vars;
247  break;
248  }
249  }
250  if (num_scalar_vars > 0 && num_field_vars > 0)
251  paramWarning("group_variables",
252  "standard variables and scalar variables are grouped together in group ",
253  i);
254  }
255 
256  for (const auto i : index_range(_group_names))
257  {
258  // Accumulate names for a given group
259  std::vector<NonlinearVariableName> names;
260  for (const auto j : index_range(_group_index))
261  if (_group_index[j] == i)
262  {
263  names.push_back(_soln_var_names[j]);
265  }
266  if (names.size() == 0)
267  mooseError("Internal error, something is wrong with variable grouping");
268  else if (names.size() == 1)
269  _group_names[i] = names[0];
270  else
271  {
272  _group_names[i] = "(";
273  for (const auto j : index_range(names))
274  {
275  _group_names[i] += names[j];
276  if (j != names.size() - 1)
277  _group_names[i] += ", ";
278  }
279  _group_names[i] += ")";
280  }
281  }
282 }
283 
284 void
286 {
287  // If no reference_vector is provided, this method is completely skipped
288 
289  auto & current_nl_sys = _fe_problem.currentNonlinearSystem();
290  auto & s = current_nl_sys.system();
291 
292  for (const auto i : index_range(_scaling_factors))
293  if (current_nl_sys.isScalarVariable(_soln_vars[i]))
294  _scaling_factors[i] = current_nl_sys.getScalarVariable(0, _soln_vars[i]).scalingFactor();
295  else
296  _scaling_factors[i] = current_nl_sys.getVariable(/*tid*/ 0, _soln_vars[i]).scalingFactor();
297 
298  std::fill(_group_resid.begin(), _group_resid.end(), 0.0);
299  std::fill(_group_ref_resid.begin(), _group_ref_resid.end(), 0.0);
300 
301  for (const auto i : index_range(_soln_vars))
302  {
303  if (_converge_on_var[i])
304  {
305  const auto group = _group_index[i];
306 
307  // Prepare residual
308  auto resid = Utility::pow<2>(s.calculate_norm(*_residual_vector, _soln_vars[i], _norm_type));
310  {
311  mooseAssert(_scaling_factors[i], "Scaling factor must not be zero");
312  resid /= Utility::pow<2>(_scaling_factors[i]);
313  }
314  _group_resid[group] += resid;
315 
316  // Prepare reference residual. If local norm, this is actually the ratio of the residual
317  // dividied by the reference at all DOF
318  Real ref_resid;
319  if (_local_norm)
320  {
321  mooseAssert((*_residual_vector).size() == (*_reference_vector).size(),
322  "Sizes of nonlinear RHS and reference vector should be the same.");
323  mooseAssert((*_reference_vector).size(), "Reference vector must be provided.");
324  auto ref = _reference_vector->clone();
325  // Add a tiny number to the reference to prevent a divide by zero.
327  auto div = (*_residual_vector).clone();
328  *div /= *ref;
329  ref_resid = Utility::pow<2>(s.calculate_norm(*div, _soln_vars[i], _norm_type));
330  }
331  else
332  {
333  ref_resid =
334  Utility::pow<2>(s.calculate_norm(*_reference_vector, _soln_vars[i], _norm_type));
336  ref_resid /= Utility::pow<2>(_scaling_factors[i]);
337  }
338  _group_ref_resid[group] += ref_resid;
339  }
340  }
341 
342  for (const auto i : index_range(_group_resid))
343  {
346  }
347 }
348 
349 void
351 {
352  // If no refernce_vector is provided, just revert to DefaultNonlinearConvergence behavior
353  if (!_reference_vector)
354  return;
355 
357 
358  std::ostringstream out;
359  out << _name << ": " << _norm_type_enum << " Reference Residual check\n";
360 
361  if (_group_names.size() > 0)
362  {
363  // Set residual and references so that they always have a spacing of 8
364  out << std::setprecision(2) << std::scientific;
365  unsigned int var_space = 0;
366  for (const auto i : index_range(_group_names))
367  if (_group_names[i].size() > var_space)
368  var_space = _group_names[i].size();
369 
370  for (const auto i : index_range(_group_names))
371  {
372  if (_converge_on_group[i])
373  {
374  // Print residual
375  out << " " << std::setw(var_space + 8) << std::right
376  << _group_names[i] + "-> res: " << std::setw(8) << _group_resid[i];
377 
378  // Print res/ref ratio
379  if (_local_norm)
380  out << " local res/ref: " << std::setw(8) << _group_ref_resid[i] << "\n";
381  else
382  {
383  // Print reference first if not local norm
384  out << " ref: " << std::setw(8) << _group_ref_resid[i];
385  out << " res/ref: " << std::setw(8)
387  << "\n";
388  }
389  }
390  }
391  _console << out.str() << std::flush;
392  }
393 }
394 
395 bool
397  const Real /*fnorm*/,
398  const Real abs_tol,
399  const Real rel_tol,
400  const Real /*initial_residual_before_preset_bcs*/)
401 {
402  // Convergence is checked via:
403  // 1) Ratio of group residual to reference is less than relative tolerance
404  // 2) if group residual is less than absolute tolerance
405  // 3) if group reference residual is zero and:
406  // 3.1) Convergence type is ZERO_TOLERANCE and group residual is zero (rare, but possible, and
407  // historically implemented that way)
408  // 3.2) Convergence type is RELATIVE_TOLERANCE and group residual
409  // is less than relative tolerance. (i.e., using the relative tolerance to check group
410  // convergence in an absolute way)
411 
412  bool convergedRelative = true;
413  for (const auto i : index_range(_group_resid))
414  convergedRelative &=
415  ((!_local_norm && _group_resid[i] < _group_ref_resid[i] * rel_tol) ||
416  (_local_norm && _group_ref_resid[i] < rel_tol) || _group_resid[i] < abs_tol ||
417  (!_group_ref_resid[i] && !_local_norm &&
420  _group_resid[i] <= rel_tol))));
421  return convergedRelative;
422 }
423 
424 bool
426  const Real fnorm,
427  const Real ref_norm,
428  const Real rel_tol,
429  const Real abs_tol,
430  std::ostringstream & oss)
431 {
432  // If no refernce_vector is provided, just revert to DefaultNonlinearConvergence behavior
433  if (!_reference_vector)
435  it, fnorm, ref_norm, rel_tol, abs_tol, oss);
436 
437  if (checkConvergenceIndividVars(fnorm, abs_tol, rel_tol, ref_norm))
438  {
439  oss << "Converged normally";
440  return true;
441  }
442  else if (it >= _accept_iters &&
444  fnorm, abs_tol * _accept_mult, rel_tol * _accept_mult, ref_norm))
445  {
446  oss << " Converged due a larger acceptable tolerance due to `acceptible_multiplier` after "
447  "`acceptible_iterations`.";
448  _console << " Converged due to ACCEPTABLE tolerances" << std::endl;
449  return true;
450  }
451 
452  return false;
453 }
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.
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
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
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
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.