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