https://mooseframework.inl.gov
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 
17 registerMooseObjectRenamed("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 
73 unsigned int
75 {
76  return 3;
77 }
78 
79 void
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 
94 void
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 
119 void
121  Real intnl,
122  std::vector<Real> & df_dintnl) const
123 {
124  df_dintnl.assign(3, -dtensile_strength(intnl));
125 }
126 
127 void
129  Real intnl,
130  std::vector<RankTwoTensor> & r) const
131 {
132  // This plasticity is associative so
133  dyieldFunction_dstressV(stress, intnl, r);
134 }
135 
136 void
138  const RankTwoTensor & stress, Real /*intnl*/, std::vector<RankFourTensor> & dr_dstress) const
139 {
140  stress.d2symmetricEigenvalues(dr_dstress);
141 }
142 
143 void
145  const RankTwoTensor & /*stress*/, Real /*intnl*/, std::vector<RankTwoTensor> & dr_dintnl) const
146 {
147  dr_dintnl.assign(3, RankTwoTensor());
148 }
149 
150 Real
152 {
153  return _strength.value(internal_param);
154 }
155 
156 Real
158 {
159  return _strength.derivative(internal_param);
160 }
161 
162 void
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 
198 Real
199 SolidMechanicsPlasticTensileMulti::dot(const std::vector<Real> & a,
200  const std::vector<Real> & b) const
201 {
202  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
203 }
204 
205 Real
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 
214 std::string
216 {
217  return "TensileMulti";
218 }
219 
220 bool
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 
256 bool
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 
411 bool
412 SolidMechanicsPlasticTensileMulti::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 
488 bool
489 SolidMechanicsPlasticTensileMulti::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 
557 bool
558 SolidMechanicsPlasticTensileMulti::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 
603 bool
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 
781 bool
783 {
784  return _use_custom_returnMap;
785 }
786 
787 bool
789 {
790  return _use_custom_cto;
791 }
virtual void yieldFunctionV(const RankTwoTensor &stress, Real intnl, std::vector< Real > &f) const override
Calculates the yield functions.
void symmetricEigenvalues(std::vector< Real > &eigvals) const
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
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 ...
void dsymmetricEigenvalues(std::vector< Real > &eigvals, std::vector< RankTwoTensorTempl< Real >> &deigvals) const
void print(std::ostream &stm=Moose::out) const
void seed(std::size_t i, unsigned int seed)
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 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 std::string modelName() const =0
void fillFromInputVector(const std::vector< T > &input, FillMethod fill_method)
Real dot(const std::vector< Real > &a, const std::vector< Real > &b) const
dot product of two 3-dimensional vectors
bool KuhnTuckerSingleSurface(Real yf, Real dpm, Real dpm_tol) const
Returns true if the Kuhn-Tucker conditions for the single surface are satisfied.
virtual Real value(Real intnl) const
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.
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...
void addRequiredParam(const std::string &name, const std::string &doc_string)
registerMooseObjectRenamed("SolidMechanicsApp", TensorMechanicsPlasticTensileMulti, "01/01/2025 00:00", SolidMechanicsPlasticTensileMulti)
static InputParameters validParams()
const bool _use_custom_returnMap
Whether to use the custom return-map algorithm.
const std::vector< double > x
virtual std::string modelName() const override
Real f(Real x)
Test function for Brents method.
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.
static const std::string mu
Definition: NS.h:123
FiniteStrainTensileMulti implements rate-independent associative tensile failure with hardening/softe...
void d2symmetricEigenvalues(std::vector< RankFourTensorTempl< Real >> &deriv) const
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.
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, since returnMap itself does not use the algorithm if _use_returnMap=false.
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 SolidMechanicsHardeningModel & _strength
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 void flowPotentialV(const RankTwoTensor &stress, Real intnl, std::vector< RankTwoTensor > &r) const override
The flow potentials.
void rotate(const TypeTensor< T > &R)
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 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.
const Real _f_tol
Tolerance on yield function.
SolidMechanicsPlasticTensileMulti(const InputParameters &parameters)
virtual Real derivative(Real intnl) const
RankTwoTensorTempl< Real > transpose() const
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.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const bool _use_custom_cto
Whether to use the custom consistent tangent operator calculation.
void symmetricEigenvaluesEigenvectors(std::vector< Real > &eigvals, RankTwoTensorTempl< Real > &eigvecs) const
const Real _shift
yield function is shifted by this amount to avoid problems with stress-derivatives at equal eigenvalu...
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.
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.
registerMooseObject("SolidMechanicsApp", SolidMechanicsPlasticTensileMulti)
static Real rand()
void mooseError(Args &&... args) const
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
void addClassDescription(const std::string &doc_string)
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
virtual unsigned int numberSurfaces() const override
The number of yield surfaces for this plasticity model.
const ConsoleStream _console
virtual Real tensile_strength(const Real internal_param) const
tensile strength as a function of residual value, rate, and internal_param
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.
Plastic Model base class The virtual functions written below must be over-ridden in derived classes t...
virtual bool useCustomCTO() const override
Returns false. You will want to override this in your derived class if you write a custom consistent ...
static const std::string k
Definition: NS.h:130
void ErrorVector unsigned int
const unsigned int _max_iters
maximum iterations allowed in the custom return-map algorithm