https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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"
16#include "Conversion.h"
17#include "MathUtils.h"
18
19#include <limits>
20#include <string>
21#include <cmath>
22#include <memory>
23
24template <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
58template <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.getObjectName())
79{
80}
81
82template <bool is_ad>
85 const GenericReal<is_ad> & /*effective_trial_stress*/) const
86{
87 return std::numeric_limits<Real>::lowest();
88}
89
90template <bool is_ad>
93 const GenericReal<is_ad> & /*effective_trial_stress*/) const
94{
95 return std::numeric_limits<Real>::max();
96}
97
98template <bool is_ad>
99void
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
161template <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 using std::isnan, std::isinf;
297 if (isnan(_residual) || isinf(MetaPhysicL::raw_value(_residual)))
298 return SolveState::NAN_INF;
299
300 if (_iteration == _max_its)
301 return SolveState::EXCEEDED_ITERATIONS;
302
303 return SolveState::SUCCESS;
304}
305
306template <bool is_ad>
307void
309 const GenericReal<is_ad> & effective_trial_stress, const GenericReal<is_ad> & scalar)
310{
311 if (_ad_derivative)
312 {
313 GenericChainedReal<is_ad> residual_and_derivative =
314 computeResidualAndDerivative(effective_trial_stress, GenericChainedReal<is_ad>(scalar, 1));
315 _residual = residual_and_derivative.value();
316 _derivative = residual_and_derivative.derivatives();
317 }
318 else
319 {
320 _residual = computeResidual(effective_trial_stress, scalar);
321 _derivative = computeDerivative(effective_trial_stress, scalar);
322 }
323}
324
325template <bool is_ad>
326bool
328 const Real reference)
329{
330 const Real residual = MetaPhysicL::raw_value(ad_residual);
331 return (std::abs(residual) <= _absolute_tolerance ||
332 std::abs(residual / reference) <= _relative_tolerance);
333}
334
335template <bool is_ad>
336bool
338 const Real reference)
339{
340 using std::abs;
341
342 // Require that we have at least done _num_resids evaluations before we allow for
343 // acceptable convergence
344 if (it < _num_resids)
345 return false;
346
347 // Check to see whether the residual has dropped by convergence_history_factor over
348 // the last _num_resids iterations. If it has (which means it's still making progress),
349 // don't consider it to be converged within the acceptable limits.
350 const Real convergence_history_factor = 10.0;
351 if (abs(_residual * convergence_history_factor) < 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
359template <bool is_ad>
360void
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
390template <bool is_ad>
391void
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
419template <bool is_ad>
420void
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
442template <bool is_ad>
443void
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
InputParameters emptyInputParameters()
void mooseError(Args &&... args)
Moose::GenericType< Real, is_ad > GenericReal
Moose::GenericType< ChainedReal, is_ad > GenericChainedReal
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
Base class that provides capability for Newton return mapping iterations on a single variable.
SingleVariableReturnMappingSolutionTempl(const InputParameters &parameters)
bool convergedAcceptable(const unsigned int it, const Real reference)
Check to see whether the residual is within acceptable convergence limits.
bool converged(const GenericReal< is_ad > &residual, const Real reference)
Check to see whether the residual is within the convergence limits.
void computeResidualAndDerivativeHelper(const GenericReal< is_ad > &effective_trial_stress, const GenericReal< is_ad > &scalar)
Helper function to compute and set the _residual and _derivative.
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.
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.
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.
virtual void outputIterationSummary(std::stringstream *iter_output, const unsigned int total_it)
Output summary information for the convergence history of the model.
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.
void returnMappingSolve(const GenericReal< is_ad > &effective_trial_stress, GenericReal< is_ad > &scalar, const ConsoleStream &console)
Perform the return mapping iterations.
virtual GenericReal< is_ad > maximumPermissibleValue(const GenericReal< is_ad > &effective_trial_stress) const
Compute the maximum permissible value of the scalar.
T sign(T x)
T clamp(const T &x, T2 lowerlimit, T2 upperlimit)
auto raw_value(const Eigen::Map< T > &in)