https://mooseframework.inl.gov
Loading...
Searching...
No Matches
GeneralizedReturnMappingSolution.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 return params;
52}
53template <bool is_ad>
55 const InputParameters & parameters)
56 : _check_range(false),
57 _line_search(true),
58 _bracket_solution(false),
59 _internal_solve_output_on(
60 parameters.get<MooseEnum>("internal_solve_output_on").getEnum<InternalSolveOutput>()),
61 _max_its(50),
62 _internal_solve_full_iteration_history(
63 parameters.get<bool>("internal_solve_full_iteration_history")),
64 _relative_tolerance(parameters.get<Real>("relative_tolerance")),
65 _absolute_tolerance(parameters.get<Real>("absolute_tolerance")),
66 _acceptable_multiplier(parameters.get<Real>("acceptable_multiplier")),
67 _num_resids(30),
68 _residual_history(_num_resids, std::numeric_limits<Real>::max()),
69 _iteration(0),
70 _initial_residual(0.0),
71 _residual(0.0),
72 _svrms_name(parameters.getObjectName())
73{
74}
75
76template <bool is_ad>
79 const GenericDenseVector<is_ad> & /*effective_trial_stress*/) const
80{
81 return std::numeric_limits<Real>::lowest();
82}
83
84template <bool is_ad>
87 const GenericDenseVector<is_ad> & /*effective_trial_stress*/) const
88{
89 return std::numeric_limits<Real>::max();
90}
91
92template <bool is_ad>
93void
95 const GenericDenseVector<is_ad> & stress_dev,
96 const GenericDenseVector<is_ad> & stress_new,
97 GenericReal<is_ad> & scalar,
98 const ConsoleStream & console)
99{
100 // construct the stringstream here only if the debug level is set to ALL
101 std::unique_ptr<std::stringstream> iter_output =
102 (_internal_solve_output_on == InternalSolveOutput::ALWAYS)
103 ? std::make_unique<std::stringstream>()
104 : nullptr;
105
106 // do the internal solve and capture iteration info during the first round
107 // iff full history output is requested regardless of whether the solve failed or succeeded
108 auto solve_state =
109 internalSolve(stress_dev,
110 stress_new,
111 scalar,
112 _internal_solve_full_iteration_history ? iter_output.get() : nullptr);
113 if (solve_state != SolveState::SUCCESS &&
114 _internal_solve_output_on != InternalSolveOutput::ALWAYS)
115 {
116 // output suppressed by user, throw immediately
117 if (_internal_solve_output_on == InternalSolveOutput::NEVER)
118 mooseException("");
119
120 // user expects some kind of output, if necessary setup output stream now
121 if (!iter_output)
122 iter_output = std::make_unique<std::stringstream>();
123
124 // add the appropriate error message to the output
125 switch (solve_state)
126 {
127 case SolveState::NAN_INF:
128 *iter_output << "Encountered inf or nan in material return mapping iterations.\n";
129 break;
130
131 case SolveState::EXCEEDED_ITERATIONS:
132 *iter_output << "Exceeded maximum iterations in material return mapping iterations.\n";
133 break;
134
135 default:
136 mooseError("Unhandled solver state");
137 }
138
139 // if full history output is only requested for failed solves we have to repeat
140 // the solve a second time
141 if (_internal_solve_full_iteration_history)
142 internalSolve(stress_dev, stress_new, scalar, iter_output.get());
143
144 // Append summary and throw exception
145 outputIterationSummary(iter_output.get(), _iteration);
146 mooseException(iter_output->str());
147 }
148
149 if (_internal_solve_output_on == InternalSolveOutput::ALWAYS)
150 {
151 // the solve did not fail but the user requested debug output anyways
152 outputIterationSummary(iter_output.get(), _iteration);
153 console << iter_output->str();
154 }
155}
156
157template <bool is_ad>
160 const GenericDenseVector<is_ad> & stress_dev,
161 const GenericDenseVector<is_ad> & stress_new,
162 GenericReal<is_ad> & delta_gamma,
163 std::stringstream * iter_output)
164{
165 delta_gamma = initialGuess(stress_dev);
166 GenericReal<is_ad> scalar_old = delta_gamma;
167 GenericReal<is_ad> scalar_increment = 0.0;
168 const GenericReal<is_ad> min_permissible_scalar = minimumPermissibleValue(stress_dev);
169 const GenericReal<is_ad> max_permissible_scalar = maximumPermissibleValue(stress_dev);
170 GenericReal<is_ad> scalar_upper_bound = max_permissible_scalar;
171 GenericReal<is_ad> scalar_lower_bound = min_permissible_scalar;
172 _iteration = 0;
173
174 _initial_residual = _residual = computeResidual(stress_dev, stress_new, delta_gamma);
175
176 Real init_resid_sign = MathUtils::sign(MetaPhysicL::raw_value(_residual));
177 Real reference_residual =
178 computeReferenceResidual(stress_dev, stress_new, _residual, delta_gamma);
179
180 if (converged(_residual, reference_residual))
181 {
182 iterationFinalize(delta_gamma);
183 outputIterationStep(iter_output, stress_dev, delta_gamma, reference_residual);
184 return SolveState::SUCCESS;
185 }
186
187 _residual_history.assign(_num_resids, std::numeric_limits<Real>::max());
188 _residual_history[0] = MetaPhysicL::raw_value(_residual);
189
190 while (_iteration < _max_its && !converged(_residual, reference_residual) &&
191 !convergedAcceptable(_iteration, reference_residual))
192 {
193 scalar_increment = -_residual / computeDerivative(stress_dev, stress_new, delta_gamma);
194 delta_gamma = scalar_old + scalar_increment;
195
196 if (_check_range)
197 checkPermissibleRange(delta_gamma,
198 scalar_increment,
199 scalar_old,
200 min_permissible_scalar,
201 max_permissible_scalar,
202 iter_output);
203
204 _residual = computeResidual(stress_dev, stress_new, delta_gamma);
205
206 reference_residual = computeReferenceResidual(stress_dev, stress_new, _residual, delta_gamma);
207 iterationFinalize(delta_gamma);
208
209 if (_bracket_solution)
210 updateBounds(delta_gamma,
211 _residual,
212 init_resid_sign,
213 scalar_upper_bound,
214 scalar_lower_bound,
215 iter_output);
216
217 if (converged(_residual, reference_residual))
218 {
219 outputIterationStep(iter_output, stress_dev, delta_gamma, reference_residual);
220 break;
221 }
222 else
223 {
224 bool modified_increment = false;
225
226 if (_bracket_solution)
227 {
228 // Check to see whether trial scalar_increment is outside the bounds, and set it to a point
229 // within the bounds if it is
230 if (scalar_old + scalar_increment >= scalar_upper_bound ||
231 scalar_old + scalar_increment <= scalar_lower_bound)
232 {
233 if (scalar_upper_bound != max_permissible_scalar &&
234 scalar_lower_bound != min_permissible_scalar)
235 {
236 const Real frac = 0.5;
237 scalar_increment =
238 (1.0 - frac) * scalar_lower_bound + frac * scalar_upper_bound - scalar_old;
239 modified_increment = true;
240 if (iter_output)
241 *iter_output << " Trial scalar_increment exceeded bounds. Setting between "
242 "lower/upper bounds. frac: "
243 << frac << std::endl;
244 }
245 }
246 }
247
248 // Update the trial scalar and recompute residual if the line search or bounds checking
249 // modified the increment
250 if (modified_increment)
251 {
252 delta_gamma = scalar_old + scalar_increment;
253 _residual = computeResidual(stress_dev, stress_new, delta_gamma);
254 reference_residual =
255 computeReferenceResidual(stress_dev, stress_new, _residual, delta_gamma);
256 iterationFinalize(delta_gamma);
257
258 if (_bracket_solution)
259 updateBounds(delta_gamma,
260 _residual,
261 init_resid_sign,
262 scalar_upper_bound,
263 scalar_lower_bound,
264 iter_output);
265 }
266 }
267
268 outputIterationStep(iter_output, stress_dev, delta_gamma, reference_residual);
269
270 ++_iteration;
271 scalar_old = delta_gamma;
272 _residual_history[_iteration % _num_resids] = MetaPhysicL::raw_value(_residual);
273 }
274
275 using std::isnan, std::isinf;
276 if (isnan(_residual) || isinf(MetaPhysicL::raw_value(_residual)))
277 return SolveState::NAN_INF;
278
279 if (_iteration == _max_its)
280 return SolveState::EXCEEDED_ITERATIONS;
281
282 return SolveState::SUCCESS;
283}
284
285template <bool is_ad>
286bool
288 const Real & reference)
289{
290 const Real residual = MetaPhysicL::raw_value(ad_residual);
291 return (std::abs(residual) <= _absolute_tolerance ||
292 std::abs(residual / reference) <= _relative_tolerance);
293}
294
295template <bool is_ad>
296bool
298 const Real & reference)
299{
300 using std::abs;
301
302 // Require that we have at least done _num_resids evaluations before we allow for
303 // acceptable convergence
304 if (it < _num_resids)
305 return false;
306
307 // Check to see whether the residual has dropped by convergence_history_factor over
308 // the last _num_resids iterations. If it has (which means it's still making progress),
309 // don't consider it to be converged within the acceptable limits.
310 const Real convergence_history_factor = 10.0;
311 if (abs(_residual * convergence_history_factor) < abs(_residual_history[(it + 1) % _num_resids]))
312 return false;
313
314 // Now that it's determined that progress is not being made, treat it as converged if
315 // we're within the acceptable convergence limits
316 return converged(_residual / _acceptable_multiplier, reference);
317}
318
319template <bool is_ad>
320void
322 GenericReal<is_ad> & scalar,
323 GenericReal<is_ad> & scalar_increment,
324 const GenericReal<is_ad> & scalar_old,
325 const GenericReal<is_ad> min_permissible_scalar,
326 const GenericReal<is_ad> max_permissible_scalar,
327 std::stringstream * iter_output)
328{
329 if (scalar > max_permissible_scalar)
330 {
331 scalar_increment = (max_permissible_scalar - scalar_old) / 2.0;
332 scalar = scalar_old + scalar_increment;
333 if (iter_output)
334 *iter_output << "Scalar greater than maximum ("
335 << MetaPhysicL::raw_value(max_permissible_scalar)
336 << ") adjusted scalar=" << MetaPhysicL::raw_value(scalar)
337 << " scalar_increment=" << MetaPhysicL::raw_value(scalar_increment) << std::endl;
338 }
339 else if (scalar < min_permissible_scalar)
340 {
341 scalar_increment = (min_permissible_scalar - scalar_old) / 2.0;
342 scalar = scalar_old + scalar_increment;
343 if (iter_output)
344 *iter_output << "Scalar less than minimum (" << MetaPhysicL::raw_value(min_permissible_scalar)
345 << ") adjusted scalar=" << MetaPhysicL::raw_value(scalar)
346 << " scalar_increment=" << MetaPhysicL::raw_value(scalar_increment) << std::endl;
347 }
348}
349
350template <bool is_ad>
351void
353 const GenericReal<is_ad> & residual,
354 const Real init_resid_sign,
355 GenericReal<is_ad> & scalar_upper_bound,
356 GenericReal<is_ad> & scalar_lower_bound,
357 std::stringstream * iter_output)
358{
359 // Update upper/lower bounds as applicable
360 if (residual * init_resid_sign < 0.0 && scalar < scalar_upper_bound)
361 {
362 scalar_upper_bound = scalar;
363 if (scalar_upper_bound < scalar_lower_bound)
364 {
365 scalar_upper_bound = scalar_lower_bound;
366 scalar_lower_bound = 0.0;
367 if (iter_output)
368 *iter_output << " Corrected for scalar_upper_bound < scalar_lower_bound" << std::endl;
369 }
370 }
371 // Don't permit setting scalar_lower_bound > scalar_upper_bound (but do permit the reverse).
372 // This ensures that if we encounter multiple roots, we pick the lowest one.
373 else if (residual * init_resid_sign > 0.0 && scalar > scalar_lower_bound &&
374 scalar < scalar_upper_bound)
375 scalar_lower_bound = scalar;
376}
377
378template <bool is_ad>
379void
381 std::stringstream * iter_output,
382 const GenericDenseVector<is_ad> & effective_trial_stress,
383 const GenericReal<is_ad> & scalar,
384 const GenericReal<is_ad> reference_residual)
385{
386 if (iter_output)
387 {
388 const unsigned int it = _iteration;
389 const Real residual = MetaPhysicL::raw_value(_residual);
390
391 *iter_output << " iteration=" << it
392 << " trial_stress=" << MetaPhysicL::raw_value(effective_trial_stress)
393 << " scalar=" << MetaPhysicL::raw_value(scalar) << " residual=" << residual
394 << " ref_res=" << reference_residual
395 << " rel_res=" << std::abs(residual) / reference_residual
396 << " rel_tol=" << _relative_tolerance << " abs_res=" << std::abs(residual)
397 << " abs_tol=" << _absolute_tolerance << '\n';
398 }
399}
400
401template <bool is_ad>
402void
404 std::stringstream * iter_output, const unsigned int total_it)
405{
406 if (iter_output)
407 *iter_output << "In " << total_it << " iterations the residual went from "
408 << MetaPhysicL::raw_value(_initial_residual) << " to "
409 << MetaPhysicL::raw_value(_residual) << " in '" << _svrms_name << "'.\n";
410}
411
InputParameters emptyInputParameters()
void mooseError(Args &&... args)
Moose::GenericType< DenseVector< Real >, is_ad > GenericDenseVector
Moose::GenericType< Real, is_ad > GenericReal
Base class that provides capability for Newton generalized (anisotropic) return mapping iterations on...
bool convergedAcceptable(const unsigned int it, const Real &reference)
Check to see whether the residual is within acceptable convergence limits.
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.
bool converged(const GenericReal< is_ad > &residual, const Real &reference)
Check to see whether the residual is within the convergence limits.
void returnMappingSolve(const GenericDenseVector< is_ad > &effective_trial_stress, const GenericDenseVector< is_ad > &stress_new, GenericReal< is_ad > &scalar, const ConsoleStream &console)
Perform the return mapping iterations.
virtual GenericReal< is_ad > maximumPermissibleValue(const GenericDenseVector< is_ad > &effective_trial_stress) const
Compute the maximum permissible value of the scalar.
virtual void outputIterationSummary(std::stringstream *iter_output, const unsigned int total_it)
Output summary information for the convergence history of the model.
virtual GenericReal< is_ad > minimumPermissibleValue(const GenericDenseVector< is_ad > &effective_trial_stress) const
Compute the minimum permissible value of the scalar.
SolveState internalSolve(const GenericDenseVector< is_ad > &effective_trial_stress, const GenericDenseVector< is_ad > &stress_new, GenericReal< is_ad > &scalar, std::stringstream *iter_output=nullptr)
Method called from within this class to perform the actual return mappping iterations.
virtual void outputIterationStep(std::stringstream *iter_output, const GenericDenseVector< is_ad > &effective_trial_stress, const GenericReal< is_ad > &scalar, const GenericReal< is_ad > reference_residual)
Output information for a single iteration step to build the convergence history of the model.
GeneralizedReturnMappingSolutionTempl(const InputParameters &parameters)
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.
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)
T sign(T x)
auto raw_value(const Eigen::Map< T > &in)