https://mooseframework.inl.gov
Loading...
Searching...
No Matches
SolidMechanicsPlasticTensileMulti.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#include "RankFourTensor.h"
12
13// Following is for perturbing eigvenvalues. This looks really bodgy, but works quite well!
14#include "MooseRandom.h"
15
17registerMooseObjectRenamed("SolidMechanicsApp",
18 TensorMechanicsPlasticTensileMulti,
19 "01/01/2025 00:00",
21
24{
26 params.addClassDescription("Associative tensile plasticity with hardening/softening");
27 params.addRequiredParam<UserObjectName>(
28 "tensile_strength",
29 "A SolidMechanicsHardening UserObject that defines hardening of the tensile strength");
30 params.addParam<Real>("shift",
31 "Yield surface is shifted by this amount to avoid problems with "
32 "defining derivatives when eigenvalues are equal. If this is "
33 "larger than f_tol, a warning will be issued. Default = f_tol.");
34 params.addParam<unsigned int>("max_iterations",
35 10,
36 "Maximum number of Newton-Raphson iterations "
37 "allowed in the custom return-map algorithm. "
38 " For highly nonlinear hardening this may "
39 "need to be higher than 10.");
40 params.addParam<bool>("use_custom_returnMap",
41 true,
42 "Whether to use the custom returnMap "
43 "algorithm. Set to true if you are using "
44 "isotropic elasticity.");
45 params.addParam<bool>("use_custom_cto",
46 true,
47 "Whether to use the custom consistent tangent "
48 "operator computations. Set to true if you are "
49 "using isotropic elasticity.");
50 return params;
51}
52
54 const InputParameters & parameters)
55 : SolidMechanicsPlasticModel(parameters),
56 _strength(getUserObject<SolidMechanicsHardeningModel>("tensile_strength")),
57 _max_iters(getParam<unsigned int>("max_iterations")),
58 _shift(parameters.isParamValid("shift") ? getParam<Real>("shift") : _f_tol),
59 _use_custom_returnMap(getParam<bool>("use_custom_returnMap")),
60 _use_custom_cto(getParam<bool>("use_custom_cto"))
61{
62 if (_shift < 0)
63 mooseError("Value of 'shift' in SolidMechanicsPlasticTensileMulti must not be negative\n");
64 if (_shift > _f_tol)
65 _console << "WARNING: value of 'shift' in SolidMechanicsPlasticTensileMulti is probably set "
66 "too high"
67 << std::endl;
68 if (LIBMESH_DIM != 3)
69 mooseError("SolidMechanicsPlasticTensileMulti is only defined for LIBMESH_DIM=3");
71}
72
73unsigned int
78
79void
81 Real intnl,
82 std::vector<Real> & f) const
83{
84 std::vector<Real> eigvals;
85 stress.symmetricEigenvalues(eigvals);
86 const Real str = tensile_strength(intnl);
87
88 f.resize(3);
89 f[0] = eigvals[0] + _shift - str;
90 f[1] = eigvals[1] - str;
91 f[2] = eigvals[2] - _shift - str;
92}
93
94void
96 const RankTwoTensor & stress, Real /*intnl*/, std::vector<RankTwoTensor> & df_dstress) const
97{
98 std::vector<Real> eigvals;
99 stress.dsymmetricEigenvalues(eigvals, df_dstress);
100
101 if (eigvals[0] > eigvals[1] - 0.1 * _shift || eigvals[1] > eigvals[2] - 0.1 * _shift)
102 {
103 Real small_perturbation;
104 RankTwoTensor shifted_stress = stress;
105 while (eigvals[0] > eigvals[1] - 0.1 * _shift || eigvals[1] > eigvals[2] - 0.1 * _shift)
106 {
107 for (unsigned i = 0; i < 3; ++i)
108 for (unsigned j = 0; j <= i; ++j)
109 {
110 small_perturbation = 0.1 * _shift * 2 * (MooseRandom::rand() - 0.5);
111 shifted_stress(i, j) += small_perturbation;
112 shifted_stress(j, i) += small_perturbation;
113 }
114 shifted_stress.dsymmetricEigenvalues(eigvals, df_dstress);
115 }
116 }
117}
118
119void
121 Real intnl,
122 std::vector<Real> & df_dintnl) const
123{
124 df_dintnl.assign(3, -dtensile_strength(intnl));
125}
126
127void
129 Real intnl,
130 std::vector<RankTwoTensor> & r) const
131{
132 // This plasticity is associative so
133 dyieldFunction_dstressV(stress, intnl, r);
134}
135
136void
138 const RankTwoTensor & stress, Real /*intnl*/, std::vector<RankFourTensor> & dr_dstress) const
139{
140 stress.d2symmetricEigenvalues(dr_dstress);
141}
142
143void
145 const RankTwoTensor & /*stress*/, Real /*intnl*/, std::vector<RankTwoTensor> & dr_dintnl) const
146{
147 dr_dintnl.assign(3, RankTwoTensor());
148}
149
150Real
152{
153 return _strength.value(internal_param);
154}
155
156Real
158{
159 return _strength.derivative(internal_param);
160}
161
162void
164 const RankTwoTensor & stress,
165 Real intnl,
166 const RankFourTensor & Eijkl,
167 std::vector<bool> & act,
168 RankTwoTensor & returned_stress) const
169{
170 act.assign(3, false);
171
172 if (f[0] <= _f_tol && f[1] <= _f_tol && f[2] <= _f_tol)
173 {
174 returned_stress = stress;
175 return;
176 }
177
178 Real returned_intnl;
179 std::vector<Real> dpm(3);
180 RankTwoTensor delta_dp;
181 std::vector<Real> yf(3);
182 bool trial_stress_inadmissible;
183 doReturnMap(stress,
184 intnl,
185 Eijkl,
186 0.0,
187 returned_stress,
188 returned_intnl,
189 dpm,
190 delta_dp,
191 yf,
192 trial_stress_inadmissible);
193
194 for (unsigned i = 0; i < 3; ++i)
195 act[i] = (dpm[i] > 0);
196}
197
198Real
200 const std::vector<Real> & b) const
201{
202 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
203}
204
205Real
207 const std::vector<Real> & b,
208 const std::vector<Real> & c) const
209{
210 return a[0] * (b[1] * c[2] - b[2] * c[1]) - a[1] * (b[0] * c[2] - b[2] * c[0]) +
211 a[2] * (b[0] * c[1] - b[1] * c[0]);
212}
213
214std::string
216{
217 return "TensileMulti";
218}
219
220bool
222 Real intnl_old,
223 const RankFourTensor & E_ijkl,
224 Real ep_plastic_tolerance,
225 RankTwoTensor & returned_stress,
226 Real & returned_intnl,
227 std::vector<Real> & dpm,
228 RankTwoTensor & delta_dp,
229 std::vector<Real> & yf,
230 bool & trial_stress_inadmissible) const
231{
233 return SolidMechanicsPlasticModel::returnMap(trial_stress,
234 intnl_old,
235 E_ijkl,
236 ep_plastic_tolerance,
237 returned_stress,
238 returned_intnl,
239 dpm,
240 delta_dp,
241 yf,
242 trial_stress_inadmissible);
243
244 return doReturnMap(trial_stress,
245 intnl_old,
246 E_ijkl,
247 ep_plastic_tolerance,
248 returned_stress,
249 returned_intnl,
250 dpm,
251 delta_dp,
252 yf,
253 trial_stress_inadmissible);
254}
255
256bool
258 Real intnl_old,
259 const RankFourTensor & E_ijkl,
260 Real ep_plastic_tolerance,
261 RankTwoTensor & returned_stress,
262 Real & returned_intnl,
263 std::vector<Real> & dpm,
264 RankTwoTensor & delta_dp,
265 std::vector<Real> & yf,
266 bool & trial_stress_inadmissible) const
267{
268 mooseAssert(dpm.size() == 3,
269 "SolidMechanicsPlasticTensileMulti size of dpm should be 3 but it is " << dpm.size());
270
271 std::vector<Real> eigvals;
272 RankTwoTensor eigvecs;
273 trial_stress.symmetricEigenvaluesEigenvectors(eigvals, eigvecs);
274 eigvals[0] += _shift;
275 eigvals[2] -= _shift;
276
277 Real str = tensile_strength(intnl_old);
278
279 yf.resize(3);
280 yf[0] = eigvals[0] - str;
281 yf[1] = eigvals[1] - str;
282 yf[2] = eigvals[2] - str;
283
284 if (yf[0] <= _f_tol && yf[1] <= _f_tol && yf[2] <= _f_tol)
285 {
286 // purely elastic (trial_stress, intnl_old)
287 trial_stress_inadmissible = false;
288 return true;
289 }
290
291 trial_stress_inadmissible = true;
292 delta_dp.zero();
293 returned_stress.zero();
294
295 // In the following i often assume that E_ijkl is
296 // for an isotropic situation. This reduces FLOPS
297 // substantially which is important since the returnMap
298 // is potentially the most compute-intensive function
299 // of a simulation.
300 // In many comments i write the general expression, and
301 // i hope that might guide future coders if they are
302 // generalising to a non-istropic E_ijkl
303
304 // n[alpha] = E_ijkl*r[alpha]_kl expressed in principal stress space
305 // (alpha = 0, 1, 2, corresponding to the three surfaces)
306 // Note that in principal stress space, the flow
307 // directions are, expressed in 'vector' form,
308 // r[0] = (1,0,0), r[1] = (0,1,0), r[2] = (0,0,1).
309 // Similar for _n:
310 // so _n[0] = E_ij00*r[0], _n[1] = E_ij11*r[1], _n[2] = E_ij22*r[2]
311 // In the following I assume that the E_ijkl is
312 // for an isotropic situation.
313 // In the anisotropic situation, we couldn't express
314 // the flow directions as vectors in the same principal
315 // stress space as the stress: they'd be full rank-2 tensors
316 std::vector<RealVectorValue> n(3);
317 n[0](0) = E_ijkl(0, 0, 0, 0);
318 n[0](1) = E_ijkl(1, 1, 0, 0);
319 n[0](2) = E_ijkl(2, 2, 0, 0);
320 n[1](0) = E_ijkl(0, 0, 1, 1);
321 n[1](1) = E_ijkl(1, 1, 1, 1);
322 n[1](2) = E_ijkl(2, 2, 1, 1);
323 n[2](0) = E_ijkl(0, 0, 2, 2);
324 n[2](1) = E_ijkl(1, 1, 2, 2);
325 n[2](2) = E_ijkl(2, 2, 2, 2);
326
327 // With non-zero Poisson's ratio and hardening
328 // it is not computationally cheap to know whether
329 // the trial stress will return to the tip, edge,
330 // or plane. The following is correct for zero
331 // Poisson's ratio and no hardening, and at least
332 // gives a not-completely-stupid guess in the
333 // more general case.
334 // trial_order[0] = type of return to try first
335 // trial_order[1] = type of return to try second
336 // trial_order[2] = type of return to try third
337 const unsigned int number_of_return_paths = 3;
338 std::vector<int> trial_order(number_of_return_paths);
339 if (yf[0] > _f_tol) // all the yield functions are positive, since eigvals are ordered eigvals[0]
340 // <= eigvals[1] <= eigvals[2]
341 {
342 trial_order[0] = tip;
343 trial_order[1] = edge;
344 trial_order[2] = plane;
345 }
346 else if (yf[1] > _f_tol) // two yield functions are positive
347 {
348 trial_order[0] = edge;
349 trial_order[1] = tip;
350 trial_order[2] = plane;
351 }
352 else
353 {
354 trial_order[0] = plane;
355 trial_order[1] = edge;
356 trial_order[2] = tip;
357 }
358
359 unsigned trial;
360 bool nr_converged = false;
361 for (trial = 0; trial < number_of_return_paths; ++trial)
362 {
363 switch (trial_order[trial])
364 {
365 case tip:
366 nr_converged = returnTip(eigvals, n, dpm, returned_stress, intnl_old, 0);
367 break;
368 case edge:
369 nr_converged = returnEdge(eigvals, n, dpm, returned_stress, intnl_old, 0);
370 break;
371 case plane:
372 nr_converged = returnPlane(eigvals, n, dpm, returned_stress, intnl_old, 0);
373 break;
374 }
375
376 str = tensile_strength(intnl_old + dpm[0] + dpm[1] + dpm[2]);
377
378 if (nr_converged && KuhnTuckerOK(returned_stress, dpm, str, ep_plastic_tolerance))
379 break;
380 }
381
382 if (trial == number_of_return_paths)
383 {
384 Moose::err << "Trial stress = \n";
385 trial_stress.print(Moose::err);
386 Moose::err << "Internal parameter = " << intnl_old << std::endl;
387 mooseError("SolidMechanicsPlasticTensileMulti: FAILURE! You probably need to implement a "
388 "line search\n");
389 // failure - must place yield function values at trial stress into yf
390 str = tensile_strength(intnl_old);
391 yf[0] = eigvals[0] - str;
392 yf[1] = eigvals[1] - str;
393 yf[2] = eigvals[2] - str;
394 return false;
395 }
396
397 // success
398
399 returned_intnl = intnl_old;
400 for (unsigned i = 0; i < 3; ++i)
401 {
402 yf[i] = returned_stress(i, i) - str;
403 delta_dp(i, i) = dpm[i];
404 returned_intnl += dpm[i];
405 }
406 returned_stress = eigvecs * returned_stress * (eigvecs.transpose());
407 delta_dp = eigvecs * delta_dp * (eigvecs.transpose());
408 return true;
409}
410
411bool
412SolidMechanicsPlasticTensileMulti::returnTip(const std::vector<Real> & eigvals,
413 const std::vector<RealVectorValue> & n,
414 std::vector<Real> & dpm,
415 RankTwoTensor & returned_stress,
416 Real intnl_old,
417 Real initial_guess) const
418{
419 // The returned point is defined by f0=f1=f2=0.
420 // that is, returned_stress = diag(str, str, str), where
421 // str = tensile_strength(intnl),
422 // where intnl = intnl_old + dpm[0] + dpm[1] + dpm[2]
423 // The 3 plastic multipliers, dpm, are defiend by the normality condition
424 // eigvals - str = dpm[0]*n[0] + dpm[1]*n[1] + dpm[2]*n[2]
425 // (Kuhn-Tucker demands that all dpm are non-negative, but we leave
426 // that checking for later.)
427 // This is a vector equation with solution (A):
428 // dpm[0] = triple(eigvals - str, n[1], n[2])/trip;
429 // dpm[1] = triple(eigvals - str, n[2], n[0])/trip;
430 // dpm[2] = triple(eigvals - str, n[0], n[1])/trip;
431 // where trip = triple(n[0], n[1], n[2]).
432 // By adding the three components of that solution together
433 // we can get an equation for x = dpm[0] + dpm[1] + dpm[2],
434 // and then our Newton-Raphson only involves one variable (x).
435 // In the following, i specialise to the isotropic situation.
436
437 Real x = initial_guess;
438 const Real denom = (n[0](0) - n[0](1)) * (n[0](0) + 2 * n[0](1));
439 Real str = tensile_strength(intnl_old + x);
440
441 if (_strength.modelName().compare("Constant") != 0)
442 {
443 // Finding x is expensive. Therefore
444 // although x!=0 for Constant Hardening, solution (A)
445 // demonstrates that we don't
446 // actually need to know x to find the dpm for
447 // Constant Hardening.
448 //
449 // However, for nontrivial Hardening, the following
450 // is necessary
451 const Real eig = eigvals[0] + eigvals[1] + eigvals[2];
452 const Real bul = (n[0](0) + 2 * n[0](1));
453
454 // and finally, the equation we want to solve is:
455 // bul*x - eig + 3*str = 0
456 // where str=tensile_strength(intnl_old + x)
457 // and x = dpm[0] + dpm[1] + dpm[2]
458 // (Note this has units of stress, so using _f_tol as a convergence check is reasonable.)
459 // Use Netwon-Raphson with initial guess x = 0
460 Real residual = bul * x - eig + 3 * str;
461 Real jacobian;
462 unsigned int iter = 0;
463 do
464 {
465 jacobian = bul + 3 * dtensile_strength(intnl_old + x);
466 x += -residual / jacobian;
467 if (iter > _max_iters) // not converging
468 return false;
469 str = tensile_strength(intnl_old + x);
470 residual = bul * x - eig + 3 * str;
471 iter++;
472 } while (residual * residual > _f_tol * _f_tol);
473 }
474
475 // The following is the solution (A) written above
476 // (dpm[0] = triple(eigvals - str, n[1], n[2])/trip, etc)
477 // in the isotropic situation
478 dpm[0] = (n[0](0) * (eigvals[0] - str) + n[0](1) * (eigvals[0] - eigvals[1] - eigvals[2] + str)) /
479 denom;
480 dpm[1] = (n[0](0) * (eigvals[1] - str) + n[0](1) * (eigvals[1] - eigvals[2] - eigvals[0] + str)) /
481 denom;
482 dpm[2] = (n[0](0) * (eigvals[2] - str) + n[0](1) * (eigvals[2] - eigvals[0] - eigvals[1] + str)) /
483 denom;
484 returned_stress(0, 0) = returned_stress(1, 1) = returned_stress(2, 2) = str;
485 return true;
486}
487
488bool
489SolidMechanicsPlasticTensileMulti::returnEdge(const std::vector<Real> & eigvals,
490 const std::vector<RealVectorValue> & n,
491 std::vector<Real> & dpm,
492 RankTwoTensor & returned_stress,
493 Real intnl_old,
494 Real initial_guess) const
495{
496 // work out the point to which we would return, "a". It is defined by
497 // f1 = 0 = f2, and the normality condition:
498 // (eigvals - a).(n1 x n2) = 0,
499 // where eigvals is the starting position
500 // (it is a vector in principal stress space).
501 // To get f1=0=f2, we need a = (a0, str, str), and a0 is found
502 // by expanding the normality condition to yield:
503 // a0 = (-str*n1xn2[1] - str*n1xn2[2] + edotn1xn2)/n1xn2[0];
504 // where edotn1xn2 = eigvals.(n1 x n2)
505 //
506 // We need to find the plastic multipliers, dpm, defined by
507 // eigvals - a = dpm[1]*n1 + dpm[2]*n2
508 // For the isotropic case, and defining eminusa = eigvals - a,
509 // the solution is easy:
510 // dpm[0] = 0;
511 // dpm[1] = (eminusa[1] - eminusa[0])/(n[1][1] - n[1][0]);
512 // dpm[2] = (eminusa[2] - eminusa[0])/(n[2][2] - n[2][0]);
513 //
514 // Now specialise to the isotropic case. Define
515 // x = dpm[1] + dpm[2] = (eigvals[1] + eigvals[2] - 2*str)/(n[0][0] + n[0][1])
516 // Notice that the RHS is a function of x, so we solve using
517 // Newton-Raphson starting with x=initial_guess
518 Real x = initial_guess;
519 const Real denom = n[0](0) + n[0](1);
520 Real str = tensile_strength(intnl_old + x);
521
522 if (_strength.modelName().compare("Constant") != 0)
523 {
524 // Finding x is expensive. Therefore
525 // although x!=0 for Constant Hardening, solution
526 // for dpm above demonstrates that we don't
527 // actually need to know x to find the dpm for
528 // Constant Hardening.
529 //
530 // However, for nontrivial Hardening, the following
531 // is necessary
532 const Real eig = eigvals[1] + eigvals[2];
533 Real residual = denom * x - eig + 2 * str;
534 Real jacobian;
535 unsigned int iter = 0;
536 do
537 {
538 jacobian = denom + 2 * dtensile_strength(intnl_old + x);
539 x += -residual / jacobian;
540 if (iter > _max_iters) // not converging
541 return false;
542 str = tensile_strength(intnl_old + x);
543 residual = denom * x - eig + 2 * str;
544 iter++;
545 } while (residual * residual > _f_tol * _f_tol);
546 }
547
548 dpm[0] = 0;
549 dpm[1] = ((eigvals[1] * n[0](0) - eigvals[2] * n[0](1)) / (n[0](0) - n[0](1)) - str) / denom;
550 dpm[2] = ((eigvals[2] * n[0](0) - eigvals[1] * n[0](1)) / (n[0](0) - n[0](1)) - str) / denom;
551
552 returned_stress(0, 0) = eigvals[0] - n[0](1) * (dpm[1] + dpm[2]);
553 returned_stress(1, 1) = returned_stress(2, 2) = str;
554 return true;
555}
556
557bool
558SolidMechanicsPlasticTensileMulti::returnPlane(const std::vector<Real> & eigvals,
559 const std::vector<RealVectorValue> & n,
560 std::vector<Real> & dpm,
561 RankTwoTensor & returned_stress,
562 Real intnl_old,
563 Real initial_guess) const
564{
565 // the returned point, "a", is defined by f2=0 and
566 // a = p - dpm[2]*n2.
567 // This is a vector equation in
568 // principal stress space, and dpm[2] is the third
569 // plasticity multiplier (dpm[0]=0=dpm[1] for return
570 // to the plane) and "p" is the starting
571 // position (p=eigvals).
572 // (Kuhn-Tucker demands that dpm[2]>=0, but we leave checking
573 // that condition for later.)
574 // Since f2=0, we must have a[2]=tensile_strength,
575 // so we can just look at the [2] component of the
576 // equation, which yields
577 // n[2][2]*dpm[2] - eigvals[2] + str = 0
578 // For hardening, str=tensile_strength(intnl_old+dpm[2]),
579 // and we want to solve for dpm[2].
580 // Use Newton-Raphson with initial guess dpm[2] = initial_guess
581 dpm[2] = initial_guess;
582 Real residual = n[2](2) * dpm[2] - eigvals[2] + tensile_strength(intnl_old + dpm[2]);
583 Real jacobian;
584 unsigned int iter = 0;
585 do
586 {
587 jacobian = n[2](2) + dtensile_strength(intnl_old + dpm[2]);
588 dpm[2] += -residual / jacobian;
589 if (iter > _max_iters) // not converging
590 return false;
591 residual = n[2](2) * dpm[2] - eigvals[2] + tensile_strength(intnl_old + dpm[2]);
592 iter++;
593 } while (residual * residual > _f_tol * _f_tol);
594
595 dpm[0] = 0;
596 dpm[1] = 0;
597 returned_stress(0, 0) = eigvals[0] - dpm[2] * n[2](0);
598 returned_stress(1, 1) = eigvals[1] - dpm[2] * n[2](1);
599 returned_stress(2, 2) = eigvals[2] - dpm[2] * n[2](2);
600 return true;
601}
602
603bool
605 const std::vector<Real> & dpm,
606 Real str,
607 Real ep_plastic_tolerance) const
608{
609 for (unsigned i = 0; i < 3; ++i)
611 returned_diagonal_stress(i, i) - str, dpm[i], ep_plastic_tolerance))
612 return false;
613 return true;
614}
615
618 const RankTwoTensor & trial_stress,
619 Real intnl_old,
620 const RankTwoTensor & stress,
621 Real intnl,
622 const RankFourTensor & E_ijkl,
623 const std::vector<Real> & cumulative_pm) const
624{
625 if (!_use_custom_cto)
627 trial_stress, intnl_old, stress, intnl, E_ijkl, cumulative_pm);
628
629 mooseAssert(cumulative_pm.size() == 3,
630 "SolidMechanicsPlasticTensileMulti size of cumulative_pm should be 3 but it is "
631 << cumulative_pm.size());
632
633 if (cumulative_pm[2] <= 0) // All cumulative_pm are non-positive, so this is admissible
634 return E_ijkl;
635
636 // Need the eigenvalues at the returned configuration
637 std::vector<Real> eigvals;
638 stress.symmetricEigenvalues(eigvals);
639
640 // need to rotate to and from principal stress space
641 // using the eigenvectors of the trial configuration
642 // (not the returned configuration).
643 std::vector<Real> trial_eigvals;
644 RankTwoTensor trial_eigvecs;
645 trial_stress.symmetricEigenvaluesEigenvectors(trial_eigvals, trial_eigvecs);
646
647 // The returnMap will have returned to the Tip, Edge or
648 // Plane. The consistentTangentOperator describes the
649 // change in stress for an arbitrary change in applied
650 // strain. I assume that the change in strain will not
651 // change the type of return (Tip remains Tip, Edge remains
652 // Edge, Plane remains Plane).
653 // I assume isotropic elasticity.
654 //
655 // The consistent tangent operator is a little different
656 // than cases where no rotation to principal stress space
657 // is made during the returnMap. Let S_ij be the stress
658 // in original coordinates, and s_ij be the stress in the
659 // principal stress coordinates, so that
660 // s_ij = diag(eigvals[0], eigvals[1], eigvals[2])
661 // We want dS_ij under an arbitrary change in strain (ep->ep+dep)
662 // dS = S(ep+dep) - S(ep)
663 // = R(ep+dep) s(ep+dep) R(ep+dep)^T - R(ep) s(ep) R(ep)^T
664 // Here R = the rotation to principal-stress space, ie
665 // R_ij = eigvecs[i][j] = i^th component of j^th eigenvector
666 // Expanding to first order in dep,
667 // dS = R(ep) (s(ep+dep) - s(ep)) R(ep)^T
668 // + dR/dep s(ep) R^T + R(ep) s(ep) dR^T/dep
669 // The first line is all that is usually calculated in the
670 // consistent tangent operator calculation, and is called
671 // cto below.
672 // The second line involves changes in the eigenvectors, and
673 // is called sec below.
674
675 RankFourTensor cto;
676 const Real hard = dtensile_strength(intnl);
677 const Real la = E_ijkl(0, 0, 1, 1);
678 const Real mu = 0.5 * (E_ijkl(0, 0, 0, 0) - la);
679
680 if (cumulative_pm[1] <= 0)
681 {
682 // only cumulative_pm[2] is positive, so this is return to the Plane
683 const Real denom = hard + la + 2 * mu;
684 const Real al = la * la / denom;
685 const Real be = la * (la + 2 * mu) / denom;
686 const Real ga = hard * (la + 2 * mu) / denom;
687 std::vector<Real> comps(9);
688 comps[0] = comps[4] = la + 2 * mu - al;
689 comps[1] = comps[3] = la - al;
690 comps[2] = comps[5] = comps[6] = comps[7] = la - be;
691 comps[8] = ga;
693 }
694 else if (cumulative_pm[0] <= 0)
695 {
696 // both cumulative_pm[2] and cumulative_pm[1] are positive, so Edge
697 const Real denom = 2 * hard + 2 * la + 2 * mu;
698 const Real al = hard * 2 * la / denom;
699 const Real be = hard * (2 * la + 2 * mu) / denom;
700 std::vector<Real> comps(9);
701 comps[0] = la + 2 * mu - 2 * la * la / denom;
702 comps[1] = comps[2] = al;
703 comps[3] = comps[6] = al;
704 comps[4] = comps[5] = comps[7] = comps[8] = be;
706 }
707 else
708 {
709 // all cumulative_pm are positive, so Tip
710 const Real denom = 3 * hard + 3 * la + 2 * mu;
711 std::vector<Real> comps(2);
712 comps[0] = hard * (3 * la + 2 * mu) / denom;
713 comps[1] = 0;
715 }
716
717 cto.rotate(trial_eigvecs);
718
719 // drdsig = change in eigenvectors under a small stress change
720 // drdsig(i,j,m,n) = dR(i,j)/dS_mn
721 // The formula below is fairly easily derived:
722 // S R = R s, so taking the variation
723 // dS R + S dR = dR s + R ds, and multiplying by R^T
724 // R^T dS R + R^T S dR = R^T dR s + ds .... (eqn 1)
725 // I demand that RR^T = 1 = R^T R, and also that
726 // (R+dR)(R+dR)^T = 1 = (R+dT)^T (R+dR), which means
727 // that dR = R*c, for some antisymmetric c, so Eqn1 reads
728 // R^T dS R + s c = c s + ds
729 // Grabbing the components of this gives ds/dS (already
730 // in RankTwoTensor), and c, which is:
731 // dR_ik/dS_mn = drdsig(i, k, m, n) = trial_eigvecs(m, b)*trial_eigvecs(n, k)*trial_eigvecs(i,
732 // b)/(trial_eigvals[k] - trial_eigvals[b]);
733 // (sum over b!=k).
734
735 RankFourTensor drdsig;
736 for (unsigned k = 0; k < 3; ++k)
737 for (unsigned b = 0; b < 3; ++b)
738 {
739 if (b == k)
740 continue;
741 for (unsigned m = 0; m < 3; ++m)
742 for (unsigned n = 0; n < 3; ++n)
743 for (unsigned i = 0; i < 3; ++i)
744 drdsig(i, k, m, n) += trial_eigvecs(m, b) * trial_eigvecs(n, k) * trial_eigvecs(i, b) /
745 (trial_eigvals[k] - trial_eigvals[b]);
746 }
747
748 // With diagla = diag(eigvals[0], eigvals[1], digvals[2])
749 // The following implements
750 // ans(i, j, a, b) += (drdsig(i, k, m, n)*trial_eigvecs(j, l)*diagla(k, l) + trial_eigvecs(i,
751 // k)*drdsig(j, l, m, n)*diagla(k, l))*E_ijkl(m, n, a, b);
752 // (sum over k, l, m and n)
753
754 RankFourTensor ans;
755 for (unsigned i = 0; i < 3; ++i)
756 for (unsigned j = 0; j < 3; ++j)
757 for (unsigned a = 0; a < 3; ++a)
758 for (unsigned k = 0; k < 3; ++k)
759 for (unsigned m = 0; m < 3; ++m)
760 ans(i, j, a, a) += (drdsig(i, k, m, m) * trial_eigvecs(j, k) +
761 trial_eigvecs(i, k) * drdsig(j, k, m, m)) *
762 eigvals[k] * la; // E_ijkl(m, n, a, b) = la*(m==n)*(a==b);
763
764 for (unsigned i = 0; i < 3; ++i)
765 for (unsigned j = 0; j < 3; ++j)
766 for (unsigned a = 0; a < 3; ++a)
767 for (unsigned b = 0; b < 3; ++b)
768 for (unsigned k = 0; k < 3; ++k)
769 {
770 ans(i, j, a, b) += (drdsig(i, k, a, b) * trial_eigvecs(j, k) +
771 trial_eigvecs(i, k) * drdsig(j, k, a, b)) *
772 eigvals[k] * mu; // E_ijkl(m, n, a, b) = mu*(m==a)*(n==b)
773 ans(i, j, a, b) += (drdsig(i, k, b, a) * trial_eigvecs(j, k) +
774 trial_eigvecs(i, k) * drdsig(j, k, b, a)) *
775 eigvals[k] * mu; // E_ijkl(m, n, a, b) = mu*(m==b)*(n==a)
776 }
777
778 return cto + ans;
779}
780
781bool
786
787bool
Real f(Real x)
Test function for Brents method.
const std::vector< double > x
const double mu
registerMooseObject("SolidMechanicsApp", SolidMechanicsPlasticTensileMulti)
registerMooseObjectRenamed("SolidMechanicsApp", TensorMechanicsPlasticTensileMulti, "01/01/2025 00:00", SolidMechanicsPlasticTensileMulti)
void ErrorVector unsigned int
const ConsoleStream _console
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
void mooseError(Args &&... args) const
static Real rand()
void seed(std::size_t i, unsigned int seed)
void fillFromInputVector(const std::vector< T > &input, FillMethod fill_method)
void rotate(const TypeTensor< T > &R)
void dsymmetricEigenvalues(std::vector< T > &eigvals, std::vector< RankTwoTensorTempl< T > > &deigvals) const
RankTwoTensorTempl< T > transpose() const
void print(std::ostream &stm=Moose::out) const
void d2symmetricEigenvalues(std::vector< RankFourTensorTempl< T > > &deriv) const
void symmetricEigenvalues(std::vector< T > &eigvals) const
void symmetricEigenvaluesEigenvectors(std::vector< T > &eigvals, RankTwoTensorTempl< T > &eigvecs) const
virtual Real derivative(Real intnl) const
virtual std::string modelName() const =0
virtual Real value(Real intnl) const
Plastic Model base class The virtual functions written below must be over-ridden in derived classes t...
virtual RankFourTensor consistentTangentOperator(const RankTwoTensor &trial_stress, Real intnl_old, const RankTwoTensor &stress, Real intnl, const RankFourTensor &E_ijkl, const std::vector< Real > &cumulative_pm) const
Calculates a custom consistent tangent operator.
virtual bool returnMap(const RankTwoTensor &trial_stress, Real intnl_old, const RankFourTensor &E_ijkl, Real ep_plastic_tolerance, RankTwoTensor &returned_stress, Real &returned_intnl, std::vector< Real > &dpm, RankTwoTensor &delta_dp, std::vector< Real > &yf, bool &trial_stress_inadmissible) const
Performs a custom return-map.
bool KuhnTuckerSingleSurface(Real yf, Real dpm, Real dpm_tol) const
Returns true if the Kuhn-Tucker conditions for the single surface are satisfied.
const Real _f_tol
Tolerance on yield function.
static InputParameters validParams()
FiniteStrainTensileMulti implements rate-independent associative tensile failure with hardening/softe...
bool returnPlane(const std::vector< Real > &eigvals, const std::vector< RealVectorValue > &n, std::vector< Real > &dpm, RankTwoTensor &returned_stress, Real intnl_old, Real initial_guess) const
Tries to return-map to the Tensile plane The return value is true if the internal Newton-Raphson proc...
virtual void dyieldFunction_dintnlV(const RankTwoTensor &stress, Real intnl, std::vector< Real > &df_dintnl) const override
The derivative of yield functions with respect to the internal parameter.
virtual bool useCustomCTO() const override
Returns false. You will want to override this in your derived class if you write a custom consistent ...
Real dot(const std::vector< Real > &a, const std::vector< Real > &b) const
dot product of two 3-dimensional vectors
const SolidMechanicsHardeningModel & _strength
const unsigned int _max_iters
maximum iterations allowed in the custom return-map algorithm
virtual void flowPotentialV(const RankTwoTensor &stress, Real intnl, std::vector< RankTwoTensor > &r) const override
The flow potentials.
virtual RankFourTensor consistentTangentOperator(const RankTwoTensor &trial_stress, Real intnl_old, const RankTwoTensor &stress, Real intnl, const RankFourTensor &E_ijkl, const std::vector< Real > &cumulative_pm) const override
Calculates a custom consistent tangent operator.
virtual Real tensile_strength(const Real internal_param) const
tensile strength as a function of residual value, rate, and internal_param
virtual void dflowPotential_dintnlV(const RankTwoTensor &stress, Real intnl, std::vector< RankTwoTensor > &dr_dintnl) const override
The derivative of the flow potential with respect to the internal parameter.
virtual bool doReturnMap(const RankTwoTensor &trial_stress, Real intnl_old, const RankFourTensor &E_ijkl, Real ep_plastic_tolerance, RankTwoTensor &returned_stress, Real &returned_intnl, std::vector< Real > &dpm, RankTwoTensor &delta_dp, std::vector< Real > &yf, bool &trial_stress_inadmissible) const
Just like returnMap, but a protected interface that definitely uses the algorithm,...
const bool _use_custom_cto
Whether to use the custom consistent tangent operator calculation.
virtual void dflowPotential_dstressV(const RankTwoTensor &stress, Real intnl, std::vector< RankFourTensor > &dr_dstress) const override
The derivative of the flow potential with respect to stress.
bool returnEdge(const std::vector< Real > &eigvals, const std::vector< RealVectorValue > &n, std::vector< Real > &dpm, RankTwoTensor &returned_stress, Real intnl_old, Real initial_guess) const
Tries to return-map to the Tensile edge.
SolidMechanicsPlasticTensileMulti(const InputParameters &parameters)
Real triple(const std::vector< Real > &a, const std::vector< Real > &b, const std::vector< Real > &c) const
triple product of three 3-dimensional vectors
bool KuhnTuckerOK(const RankTwoTensor &returned_diagonal_stress, const std::vector< Real > &dpm, Real str, Real ep_plastic_tolerance) const
Returns true if the Kuhn-Tucker conditions are satisfied.
virtual void yieldFunctionV(const RankTwoTensor &stress, Real intnl, std::vector< Real > &f) const override
Calculates the yield functions.
const Real _shift
yield function is shifted by this amount to avoid problems with stress-derivatives at equal eigenvalu...
virtual void activeConstraints(const std::vector< Real > &f, const RankTwoTensor &stress, Real intnl, const RankFourTensor &Eijkl, std::vector< bool > &act, RankTwoTensor &returned_stress) const override
The active yield surfaces, given a vector of yield functions.
const bool _use_custom_returnMap
Whether to use the custom return-map algorithm.
bool returnTip(const std::vector< Real > &eigvals, const std::vector< RealVectorValue > &n, std::vector< Real > &dpm, RankTwoTensor &returned_stress, Real intnl_old, Real initial_guess) const
Tries to return-map to the Tensile tip.
virtual unsigned int numberSurfaces() const override
The number of yield surfaces for this plasticity model.
virtual bool returnMap(const RankTwoTensor &trial_stress, Real intnl_old, const RankFourTensor &E_ijkl, Real ep_plastic_tolerance, RankTwoTensor &returned_stress, Real &returned_intnl, std::vector< Real > &dpm, RankTwoTensor &delta_dp, std::vector< Real > &yf, bool &trial_stress_inadmissible) const override
Performs a custom return-map.
virtual std::string modelName() const override
virtual void dyieldFunction_dstressV(const RankTwoTensor &stress, Real intnl, std::vector< RankTwoTensor > &df_dstress) const override
The derivative of yield functions with respect to stress.
virtual bool useCustomReturnMap() const override
Returns false. You will want to override this in your derived class if you write a custom returnMap f...
virtual Real dtensile_strength(const Real internal_param) const
d(tensile strength)/d(internal_param) as a function of residual value, rate, and internal_param