https://mooseframework.inl.gov
SingleVariableReturnMappingSolution.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 
11 
12 #include "Moose.h"
13 #include "MooseEnum.h"
14 #include "MooseObject.h"
15 #include "ConsoleStreamInterface.h"
16 #include "Conversion.h"
17 #include "MathUtils.h"
18 
19 #include <limits>
20 #include <string>
21 #include <cmath>
22 #include <memory>
23 
24 template <bool is_ad>
27 {
29  params.addParam<Real>(
30  "relative_tolerance", 1e-8, "Relative convergence tolerance for Newton iteration");
31  params.addParam<Real>(
32  "absolute_tolerance", 1e-11, "Absolute convergence tolerance for Newton iteration");
33  params.addParam<Real>("acceptable_multiplier",
34  10,
35  "Factor applied to relative and absolute "
36  "tolerance for acceptable convergence if "
37  "iterations are no longer making progress");
38 
39  // diagnostic output parameters
40  MooseEnum internal_solve_output_on_enum("never on_error always", "on_error");
41  params.addParam<MooseEnum>("internal_solve_output_on",
42  internal_solve_output_on_enum,
43  "When to output internal Newton solve information");
44  params.addParam<bool>("internal_solve_full_iteration_history",
45  false,
46  "Set true to output full internal Newton iteration history at times "
47  "determined by `internal_solve_output_on`. If false, only a summary is "
48  "output.");
49  params.addParamNamesToGroup("internal_solve_output_on internal_solve_full_iteration_history",
50  "Debug");
51 
52  params.addParam<bool>("automatic_differentiation_return_mapping",
53  false,
54  "Whether to use automatic differentiation to compute the derivative.");
55  return params;
56 }
57 
58 template <bool is_ad>
60  const InputParameters & parameters)
61  : _check_range(false),
62  _line_search(true),
63  _bracket_solution(true),
64  _internal_solve_output_on(
65  parameters.get<MooseEnum>("internal_solve_output_on").getEnum<InternalSolveOutput>()),
66  _max_its(1000), // Far larger than ever expected to be needed
67  _internal_solve_full_iteration_history(
68  parameters.get<bool>("internal_solve_full_iteration_history")),
69  _relative_tolerance(parameters.get<Real>("relative_tolerance")),
70  _absolute_tolerance(parameters.get<Real>("absolute_tolerance")),
71  _acceptable_multiplier(parameters.get<Real>("acceptable_multiplier")),
72  _ad_derivative(parameters.get<bool>("automatic_differentiation_return_mapping")),
73  _num_resids(30),
74  _residual_history(_num_resids, std::numeric_limits<Real>::max()),
75  _iteration(0),
76  _initial_residual(0.0),
77  _residual(0.0),
78  _svrms_name(parameters.get<std::string>("_object_name"))
79 {
80 }
81 
82 template <bool is_ad>
85  const GenericReal<is_ad> & /*effective_trial_stress*/) const
86 {
87  return std::numeric_limits<Real>::lowest();
88 }
89 
90 template <bool is_ad>
93  const GenericReal<is_ad> & /*effective_trial_stress*/) const
94 {
95  return std::numeric_limits<Real>::max();
96 }
97 
98 template <bool is_ad>
99 void
101  const GenericReal<is_ad> & effective_trial_stress,
102  GenericReal<is_ad> & scalar,
103  const ConsoleStream & console)
104 {
105  // construct the stringstream here only if the debug level is set to ALL
106  std::unique_ptr<std::stringstream> iter_output =
107  (_internal_solve_output_on == InternalSolveOutput::ALWAYS)
108  ? std::make_unique<std::stringstream>()
109  : nullptr;
110 
111  // do the internal solve and capture iteration info during the first round
112  // iff full history output is requested regardless of whether the solve failed or succeeded
113  auto solve_state =
114  internalSolve(effective_trial_stress,
115  scalar,
116  _internal_solve_full_iteration_history ? iter_output.get() : nullptr);
117  if (solve_state != SolveState::SUCCESS &&
118  _internal_solve_output_on != InternalSolveOutput::ALWAYS)
119  {
120  // output suppressed by user, throw immediately
121  if (_internal_solve_output_on == InternalSolveOutput::NEVER)
122  mooseException("");
123 
124  // user expects some kind of output, if necessary setup output stream now
125  if (!iter_output)
126  iter_output = std::make_unique<std::stringstream>();
127 
128  // add the appropriate error message to the output
129  switch (solve_state)
130  {
131  case SolveState::NAN_INF:
132  *iter_output << "Encountered inf or nan in material return mapping iterations.\n";
133  break;
134 
135  case SolveState::EXCEEDED_ITERATIONS:
136  *iter_output << "Exceeded maximum iterations in material return mapping iterations.\n";
137  break;
138 
139  default:
140  mooseError("Unhandled solver state");
141  }
142 
143  // if full history output is only requested for failed solves we have to repeat
144  // the solve a second time
145  if (_internal_solve_full_iteration_history)
146  internalSolve(effective_trial_stress, scalar, iter_output.get());
147 
148  // Append summary and throw exception
149  outputIterationSummary(iter_output.get(), _iteration);
150  mooseException(iter_output->str());
151  }
152 
153  if (_internal_solve_output_on == InternalSolveOutput::ALWAYS)
154  {
155  // the solve did not fail but the user requested debug output anyways
156  outputIterationSummary(iter_output.get(), _iteration);
157  console << iter_output->str() << std::flush;
158  }
159 }
160 
161 template <bool is_ad>
164  const GenericReal<is_ad> effective_trial_stress,
165  GenericReal<is_ad> & scalar,
166  std::stringstream * iter_output)
167 {
168  scalar = initialGuess(effective_trial_stress);
169  GenericReal<is_ad> scalar_old = scalar;
170  GenericReal<is_ad> scalar_increment = 0.0;
171  const GenericReal<is_ad> min_permissible_scalar = minimumPermissibleValue(effective_trial_stress);
172  const GenericReal<is_ad> max_permissible_scalar = maximumPermissibleValue(effective_trial_stress);
173  GenericReal<is_ad> scalar_upper_bound = max_permissible_scalar;
174  GenericReal<is_ad> scalar_lower_bound = min_permissible_scalar;
175  _iteration = 0;
176 
177  computeResidualAndDerivativeHelper(effective_trial_stress, scalar);
178  _initial_residual = _residual;
179 
180  GenericReal<is_ad> residual_old = _residual;
181  Real init_resid_sign = MathUtils::sign(MetaPhysicL::raw_value(_residual));
182  Real reference_residual = computeReferenceResidual(effective_trial_stress, scalar);
183 
184  if (converged(_residual, reference_residual))
185  {
186  iterationFinalize(scalar);
187  outputIterationStep(iter_output, effective_trial_stress, scalar, reference_residual);
188  return SolveState::SUCCESS;
189  }
190 
191  _residual_history.assign(_num_resids, std::numeric_limits<Real>::max());
192  _residual_history[0] = MetaPhysicL::raw_value(_residual);
193 
194  while (_iteration < _max_its && !converged(_residual, reference_residual) &&
195  !convergedAcceptable(_iteration, reference_residual))
196  {
197  preStep(scalar_old, _residual, _derivative);
198 
199  scalar_increment = -_residual / _derivative;
200  scalar = scalar_old + scalar_increment;
201 
202  if (_check_range)
203  checkPermissibleRange(scalar,
204  scalar_increment,
205  scalar_old,
206  min_permissible_scalar,
207  max_permissible_scalar,
208  iter_output);
209 
210  computeResidualAndDerivativeHelper(effective_trial_stress, scalar);
211  reference_residual = computeReferenceResidual(effective_trial_stress, scalar);
212  iterationFinalize(scalar);
213 
214  if (_bracket_solution)
215  updateBounds(
216  scalar, _residual, init_resid_sign, scalar_upper_bound, scalar_lower_bound, iter_output);
217 
218  if (converged(_residual, reference_residual))
219  {
220  outputIterationStep(iter_output, effective_trial_stress, scalar, reference_residual);
221  break;
222  }
223  else
224  {
225  bool modified_increment = false;
226 
227  // Line Search
228  if (_line_search)
229  {
230  if (residual_old - _residual != 0.0)
231  {
232  GenericReal<is_ad> alpha = residual_old / (residual_old - _residual);
233  alpha = MathUtils::clamp(alpha, 1.0e-2, 1.0);
234 
235  if (alpha != 1.0)
236  {
237  modified_increment = true;
238  scalar_increment *= alpha;
239  if (iter_output)
240  *iter_output << " Line search alpha = " << MetaPhysicL::raw_value(alpha)
241  << " increment = " << MetaPhysicL::raw_value(scalar_increment)
242  << std::endl;
243  }
244  }
245  }
246 
247  if (_bracket_solution)
248  {
249  // Check to see whether trial scalar_increment is outside the bounds, and set it to a point
250  // within the bounds if it is
251  if (scalar_old + scalar_increment >= scalar_upper_bound ||
252  scalar_old + scalar_increment <= scalar_lower_bound)
253  {
254  if (scalar_upper_bound != max_permissible_scalar &&
255  scalar_lower_bound != min_permissible_scalar)
256  {
257  const Real frac = 0.5;
258  scalar_increment =
259  (1.0 - frac) * scalar_lower_bound + frac * scalar_upper_bound - scalar_old;
260  modified_increment = true;
261  if (iter_output)
262  *iter_output << " Trial scalar_increment exceeded bounds. Setting between "
263  "lower/upper bounds. frac: "
264  << frac << std::endl;
265  }
266  }
267  }
268 
269  // Update the trial scalar and recompute residual if the line search or bounds checking
270  // modified the increment
271  if (modified_increment)
272  {
273  scalar = scalar_old + scalar_increment;
274  computeResidualAndDerivativeHelper(effective_trial_stress, scalar);
275  reference_residual = computeReferenceResidual(effective_trial_stress, scalar);
276  iterationFinalize(scalar);
277 
278  if (_bracket_solution)
279  updateBounds(scalar,
280  _residual,
281  init_resid_sign,
282  scalar_upper_bound,
283  scalar_lower_bound,
284  iter_output);
285  }
286  }
287 
288  outputIterationStep(iter_output, effective_trial_stress, scalar, reference_residual);
289 
290  ++_iteration;
291  residual_old = _residual;
292  scalar_old = scalar;
293  _residual_history[_iteration % _num_resids] = MetaPhysicL::raw_value(_residual);
294  }
295 
296  if (std::isnan(_residual) || std::isinf(MetaPhysicL::raw_value(_residual)))
297  return SolveState::NAN_INF;
298 
299  if (_iteration == _max_its)
300  return SolveState::EXCEEDED_ITERATIONS;
301 
302  return SolveState::SUCCESS;
303 }
304 
305 template <bool is_ad>
306 void
308  const GenericReal<is_ad> & effective_trial_stress, const GenericReal<is_ad> & scalar)
309 {
310  if (_ad_derivative)
311  {
312  GenericChainedReal<is_ad> residual_and_derivative =
313  computeResidualAndDerivative(effective_trial_stress, GenericChainedReal<is_ad>(scalar, 1));
314  _residual = residual_and_derivative.value();
315  _derivative = residual_and_derivative.derivatives();
316  }
317  else
318  {
319  _residual = computeResidual(effective_trial_stress, scalar);
320  _derivative = computeDerivative(effective_trial_stress, scalar);
321  }
322 }
323 
324 template <bool is_ad>
325 bool
327  const Real reference)
328 {
329  const Real residual = MetaPhysicL::raw_value(ad_residual);
330  return (std::abs(residual) <= _absolute_tolerance ||
331  std::abs(residual / reference) <= _relative_tolerance);
332 }
333 
334 template <bool is_ad>
335 bool
337  const Real reference)
338 {
339  // Require that we have at least done _num_resids evaluations before we allow for
340  // acceptable convergence
341  if (it < _num_resids)
342  return false;
343 
344  // Check to see whether the residual has dropped by convergence_history_factor over
345  // the last _num_resids iterations. If it has (which means it's still making progress),
346  // don't consider it to be converged within the acceptable limits.
347  const Real convergence_history_factor = 10.0;
348  if (std::abs(_residual * convergence_history_factor) <
349  std::abs(_residual_history[(it + 1) % _num_resids]))
350  return false;
351 
352  // Now that it's determined that progress is not being made, treat it as converged if
353  // we're within the acceptable convergence limits
354  return converged(_residual / _acceptable_multiplier, reference);
355 }
356 
357 template <bool is_ad>
358 void
360  GenericReal<is_ad> & scalar,
361  GenericReal<is_ad> & scalar_increment,
362  const GenericReal<is_ad> & scalar_old,
363  const GenericReal<is_ad> min_permissible_scalar,
364  const GenericReal<is_ad> max_permissible_scalar,
365  std::stringstream * iter_output)
366 {
367  if (scalar > max_permissible_scalar)
368  {
369  scalar_increment = (max_permissible_scalar - scalar_old) / 2.0;
370  scalar = scalar_old + scalar_increment;
371  if (iter_output)
372  *iter_output << "Scalar greater than maximum ("
373  << MetaPhysicL::raw_value(max_permissible_scalar)
374  << ") adjusted scalar=" << MetaPhysicL::raw_value(scalar)
375  << " scalar_increment=" << MetaPhysicL::raw_value(scalar_increment) << std::endl;
376  }
377  else if (scalar < min_permissible_scalar)
378  {
379  scalar_increment = (min_permissible_scalar - scalar_old) / 2.0;
380  scalar = scalar_old + scalar_increment;
381  if (iter_output)
382  *iter_output << "Scalar less than minimum (" << MetaPhysicL::raw_value(min_permissible_scalar)
383  << ") adjusted scalar=" << MetaPhysicL::raw_value(scalar)
384  << " scalar_increment=" << MetaPhysicL::raw_value(scalar_increment) << std::endl;
385  }
386 }
387 
388 template <bool is_ad>
389 void
391  const GenericReal<is_ad> & scalar,
392  const GenericReal<is_ad> & residual,
393  const Real init_resid_sign,
394  GenericReal<is_ad> & scalar_upper_bound,
395  GenericReal<is_ad> & scalar_lower_bound,
396  std::stringstream * iter_output)
397 {
398  // Update upper/lower bounds as applicable
399  if (residual * init_resid_sign < 0.0 && scalar < scalar_upper_bound)
400  {
401  scalar_upper_bound = scalar;
402  if (scalar_upper_bound < scalar_lower_bound)
403  {
404  scalar_upper_bound = scalar_lower_bound;
405  scalar_lower_bound = 0.0;
406  if (iter_output)
407  *iter_output << " Corrected for scalar_upper_bound < scalar_lower_bound" << std::endl;
408  }
409  }
410  // Don't permit setting scalar_lower_bound > scalar_upper_bound (but do permit the reverse).
411  // This ensures that if we encounter multiple roots, we pick the lowest one.
412  else if (residual * init_resid_sign > 0.0 && scalar > scalar_lower_bound &&
413  scalar < scalar_upper_bound)
414  scalar_lower_bound = scalar;
415 }
416 
417 template <bool is_ad>
418 void
420  std::stringstream * iter_output,
421  const GenericReal<is_ad> & effective_trial_stress,
422  const GenericReal<is_ad> & scalar,
423  const Real reference_residual)
424 {
425  if (iter_output)
426  {
427  const unsigned int it = _iteration;
428  const Real residual = MetaPhysicL::raw_value(_residual);
429 
430  *iter_output << " iteration=" << it
431  << " trial_stress=" << MetaPhysicL::raw_value(effective_trial_stress)
432  << " scalar=" << MetaPhysicL::raw_value(scalar) << " residual=" << residual
433  << " ref_res=" << reference_residual
434  << " rel_res=" << std::abs(residual) / reference_residual
435  << " rel_tol=" << _relative_tolerance << " abs_res=" << std::abs(residual)
436  << " abs_tol=" << _absolute_tolerance << '\n';
437  }
438 }
439 
440 template <bool is_ad>
441 void
443  std::stringstream * iter_output, const unsigned int total_it)
444 {
445  if (iter_output)
446  *iter_output << "In " << total_it << " iterations the residual went from "
447  << MetaPhysicL::raw_value(_initial_residual) << " to "
448  << MetaPhysicL::raw_value(_residual) << " in '" << _svrms_name << "'."
449  << std::endl;
450 }
451 
Moose::GenericType< Real, is_ad > GenericReal
virtual void outputIterationStep(std::stringstream *iter_output, const GenericReal< is_ad > &effective_trial_stress, const GenericReal< is_ad > &scalar, const Real reference_residual)
Output information for a single iteration step to build the convergence history of the model...
virtual GenericReal< is_ad > maximumPermissibleValue(const GenericReal< is_ad > &effective_trial_stress) const
Compute the maximum permissible value of the scalar.
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void mooseError(Args &&... args)
bool convergedAcceptable(const unsigned int it, const Real reference)
Check to see whether the residual is within acceptable convergence limits.
auto raw_value(const Eigen::Map< T > &in)
SolveState internalSolve(const GenericReal< is_ad > effective_trial_stress, GenericReal< is_ad > &scalar, std::stringstream *iter_output=nullptr)
Method called from within this class to perform the actual return mappping iterations.
void checkPermissibleRange(GenericReal< is_ad > &scalar, GenericReal< is_ad > &scalar_increment, const GenericReal< is_ad > &scalar_old, const GenericReal< is_ad > min_permissible_scalar, const GenericReal< is_ad > max_permissible_scalar, std::stringstream *iter_output)
Check to see whether solution is within admissible range, and set it within that range if it is not...
InputParameters emptyInputParameters()
auto max(const L &left, const R &right)
void updateBounds(const GenericReal< is_ad > &scalar, const GenericReal< is_ad > &residual, const Real init_resid_sign, GenericReal< is_ad > &scalar_upper_bound, GenericReal< is_ad > &scalar_lower_bound, std::stringstream *iter_output)
Update the upper and lower bounds of the root for the effective inelastic strain. ...
virtual GenericReal< is_ad > minimumPermissibleValue(const GenericReal< is_ad > &effective_trial_stress) const
Compute the minimum permissible value of the scalar.
bool converged(const std::vector< std::pair< unsigned int, Real >> &residuals, const std::vector< Real > &abs_tolerances)
Based on the residuals, determine if the iterative process converged or not.
void returnMappingSolve(const GenericReal< is_ad > &effective_trial_stress, GenericReal< is_ad > &scalar, const ConsoleStream &console)
Perform the return mapping iterations.
T sign(T x)
virtual void outputIterationSummary(std::stringstream *iter_output, const unsigned int total_it)
Output summary information for the convergence history of the model.
Base class that provides capability for Newton return mapping iterations on a single variable...
SingleVariableReturnMappingSolutionTempl(const InputParameters &parameters)
Moose::GenericType< ChainedReal, is_ad > GenericChainedReal
void computeResidualAndDerivativeHelper(const GenericReal< is_ad > &effective_trial_stress, const GenericReal< is_ad > &scalar)
Helper function to compute and set the _residual and _derivative.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
static const std::string alpha
Definition: NS.h:134
bool converged(const GenericReal< is_ad > &residual, const Real reference)
Check to see whether the residual is within the convergence limits.
T clamp(const T &x, T2 lowerlimit, T2 upperlimit)
const Elem & get(const ElemType type_in)
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)