https://mooseframework.inl.gov
ComputeLagrangianStrainBase.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 
12 #include "MathUtils.h"
13 #include "PermutationTensor.h"
14 
15 template <class G>
18 {
20 
21  params.addRequiredCoupledVar("displacements", "Displacement variables");
22  params.addParam<bool>(
23  "large_kinematics", false, "Use large displacement kinematics in the kernel.");
24  params.addParam<bool>("stabilize_strain", false, "Average the volumetric strains");
25  MooseEnum F_bar_mode("total incremental", "total");
26  params.addParam<MooseEnum>(
27  "F_bar_mode",
28  F_bar_mode,
29  "What deformation gradient F-bar averages over (only used when `stabilize_strain = true`). "
30  "'total' (default) averages the full F at each qp and rescales each qp's F by "
31  "cbrt(det(F_avg)/det(F_ust)). 'incremental' averages the incremental F "
32  "(F_ust * F_ust_old^{-1}) at each qp and rescales by cbrt(det(f_avg)/det(f_ust)); this is "
33  "bit-for-bit compatible with the OLD `ComputeFiniteStrain` + `volumetric_locking_correction "
34  "= "
35  "true` formulation. Set to 'incremental' when cross-checking against the old kernel system.");
36  params.addParam<bool>(
37  "publish_rotation_increment",
38  false,
39  "If true, publish `rotation_increment = exp(vorticity_increment)` (Rodrigues) for "
40  "downstream consumers that rotate by it (e.g. `ComputeMultiPlasticityStress` with "
41  "`perform_finite_strain_rotations = true`). Default false keeps `rotation_increment = I` "
42  "(the historical behavior -- the Lagrangian objective-rate machinery applies rotation "
43  "externally). Enable when wrapping plasticity that needs its internal stress state to "
44  "track the rotated Cauchy stress between steps, in tandem with `rotate_old_stress = true` "
45  "on the objective rate.");
46  params.addRangeCheckedParam<Real>(
47  "alpha",
48  1.0,
49  "alpha >= 0.5 & alpha <= 1.0",
50  "Generalized midpoint weight for the deformation gradient. 1.0 = backward Euler (default), "
51  "0.5 = midpoint rule (matches Abaqus/Implicit).");
52  MooseEnum kinematic_approximation("linear quadratic rashid_approximate rashid_eigen", "linear");
53  params.addParam<MooseEnum>(
54  "kinematic_approximation",
55  kinematic_approximation,
56  "Approximation to the increment in the spatial velocity gradient: 'linear' (default; "
57  "dL = I - f^{-1}), 'quadratic' (one more Taylor term), 'rashid_approximate' (Rashid's "
58  "symmetric+skew formulas), or 'rashid_eigen' (exact log f via polar decomposition + "
59  "matrix logs). Only affects large_kinematics; small kinematics is always linear.");
60  params.addParam<std::vector<MaterialPropertyName>>(
61  "eigenstrain_names", {}, "List of eigenstrains to account for");
62  params.addParam<std::vector<MaterialPropertyName>>(
63  "homogenization_gradient_names",
64  {},
65  "List of homogenization gradients to add to the displacement gradient");
66 
67  params.addParam<std::string>("base_name", "Material property base name");
68 
69  // We rely on this *not* having use_displaced mesh on
70  params.suppressParameter<bool>("use_displaced_mesh");
71 
72  return params;
73 }
74 
75 template <class G>
77  : Material(parameters),
78  GuaranteeProvider(this),
79  _ndisp(coupledComponents("displacements")),
80  _disp(coupledValues("displacements")),
81  _grad_disp(coupledGradients("displacements")),
82  _base_name(isParamValid("base_name") ? getParam<std::string>("base_name") + "_" : ""),
83  _large_kinematics(getParam<bool>("large_kinematics")),
84  _stabilize_strain(getParam<bool>("stabilize_strain")),
85  _F_bar_mode(getParam<MooseEnum>("F_bar_mode").template getEnum<FBarMode>()),
86  _publish_rotation_increment(getParam<bool>("publish_rotation_increment")),
87  _alpha(getParam<Real>("alpha")),
88  _kinematic_approximation(
89  getParam<MooseEnum>("kinematic_approximation").template getEnum<KinematicApproximation>()),
90  _eigenstrain_names(getParam<std::vector<MaterialPropertyName>>("eigenstrain_names")),
91  _eigenstrains(_eigenstrain_names.size()),
92  _eigenstrains_old(_eigenstrain_names.size()),
93  _total_strain(declareProperty<RankTwoTensor>(_base_name + "total_strain")),
94  _total_strain_old(getMaterialPropertyOld<RankTwoTensor>(_base_name + "total_strain")),
95  _mechanical_strain(declareProperty<RankTwoTensor>(_base_name + "mechanical_strain")),
96  _mechanical_strain_old(getMaterialPropertyOld<RankTwoTensor>(_base_name + "mechanical_strain")),
97  _rotated_mechanical_strain(
98  declareProperty<RankTwoTensor>(_base_name + "rotated_mechanical_strain")),
99  _rotated_mechanical_strain_old(
100  getMaterialPropertyOld<RankTwoTensor>(_base_name + "rotated_mechanical_strain")),
101  _strain_increment(declareProperty<RankTwoTensor>(_base_name + "strain_increment")),
102  _deformation_gradient_increment(
103  declareProperty<RankTwoTensor>(_base_name + "spatial_deformation_gradient_increment")),
104  _vorticity_increment(declareProperty<RankTwoTensor>(_base_name + "vorticity_increment")),
105  _F_ust(declareProperty<RankTwoTensor>(_base_name + "unstabilized_deformation_gradient")),
106  _F_ust_old(
107  getMaterialPropertyOld<RankTwoTensor>(_base_name + "unstabilized_deformation_gradient")),
108  _F_actual(declareProperty<RankTwoTensor>(_base_name + "actual_deformation_gradient")),
109  _F_avg(declareProperty<RankTwoTensor>(_base_name + "average_deformation_gradient")),
110  _F(declareProperty<RankTwoTensor>(_base_name + "deformation_gradient")),
111  _F_old(getMaterialPropertyOld<RankTwoTensor>(_base_name + "deformation_gradient")),
112  _F_inv(declareProperty<RankTwoTensor>(_base_name + "inverse_deformation_gradient")),
113  _f_inv(declareProperty<RankTwoTensor>(_base_name + "inverse_incremental_deformation_gradient")),
114  _F_ust_inv(
115  declareProperty<RankTwoTensor>(_base_name + "inverse_unstabilized_deformation_gradient")),
116  _F_ust_det(declareProperty<Real>(_base_name + "det_unstabilized_deformation_gradient")),
117  _d_deformation_gradient_increment_d_F(declareProperty<RankFourTensor>(
118  _base_name + "d_spatial_deformation_gradient_increment_d_deformation_gradient")),
119  _d_vorticity_increment_d_F(declareProperty<RankFourTensor>(
120  _base_name + "d_vorticity_increment_d_deformation_gradient")),
121  _d_F_d_grad_u(
122  declareProperty<RankFourTensor>(_base_name + "d_deformation_gradient_d_grad_displacement")),
123  _rotation(declareProperty<RankTwoTensor>(_base_name + "rotation")),
124  _stretch(declareProperty<RankTwoTensor>(_base_name + "stretch")),
125  _d_rotation_d_F(
126  declareProperty<RankFourTensor>(_base_name + "d_rotation_d_deformation_gradient")),
127  _d_F_stab_d_F_ust(declareProperty<RankFourTensor>(_base_name + "d_F_stab_d_F_unstabilized")),
128  _d_F_stab_d_F_avg(declareProperty<RankFourTensor>(_base_name + "d_F_stab_d_F_average")),
129  _homogenization_gradient_names(
130  getParam<std::vector<MaterialPropertyName>>("homogenization_gradient_names")),
131  _homogenization_contributions(_homogenization_gradient_names.size()),
132  _rotation_increment(declareProperty<RankTwoTensor>(_base_name + "rotation_increment"))
133 {
134  // Couple old displacements only when the simulation is transient. With a Steady executioner
135  // there is no "previous step", and the generalized midpoint rule treats the old state as
136  // the undeformed reference (u_n = 0, grad u_n = 0, so F_n = I). The (1 - alpha) contribution
137  // is then identically zero and we skip it in computeQpUnstabilizedDeformationGradient.
138  if (_fe_problem.isTransient())
139  {
140  _disp_old = coupledValuesOld("displacements");
141  _grad_disp_old = coupledGradientsOld("displacements");
142  }
143 
144  // Setup eigenstrains
145  for (auto i : make_range(_eigenstrain_names.size()))
146  {
147  _eigenstrains[i] = &getMaterialProperty<RankTwoTensor>(_eigenstrain_names[i]);
148  _eigenstrains_old[i] = &getMaterialPropertyOld<RankTwoTensor>(_eigenstrain_names[i]);
149  }
150 
151  // In the future maybe there is a reason to have more than one, but for now
152  if (_homogenization_gradient_names.size() > 1)
153  mooseError("ComputeLagrangianStrainBase cannot accommodate more than one "
154  "homogenization gradient");
155 
156  // Setup homogenization contributions
157  for (unsigned int i = 0; i < _homogenization_gradient_names.size(); i++)
159  &getMaterialProperty<RankTwoTensor>(_homogenization_gradient_names[i]);
160 
161  // The strain calculator is the single source of truth for the kinematics regime. Publish a
162  // LARGE_KINEMATICS guarantee on the deformation gradient (issued in the constructor so it is in
163  // place before any consumer's initialSetup); the Lagrangian stress calculators and
164  // stress-divergence kernels derive their own `large_kinematics` from it. Small kinematics leaves
165  // the guarantee absent, which the consumers read as `large_kinematics = false`.
166  if (_large_kinematics)
167  issueGuarantee(_base_name + "deformation_gradient", Guarantee::LARGE_KINEMATICS);
168 }
169 
170 template <class G>
171 void
173 {
174  _total_strain[_qp].zero();
175  _mechanical_strain[_qp].zero();
176  _rotated_mechanical_strain[_qp].zero();
177  _F[_qp].setToIdentity();
178  _F_ust[_qp].setToIdentity();
179  _rotation[_qp].setToIdentity();
180 }
181 
182 template <class G>
183 void
185 {
186  // Average the volumetric terms, if required
187  computeDeformationGradient();
188 
189  for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
190  computeQpProperties();
191 }
192 
193 template <class G>
194 void
196 {
197  // Add in the macroscale gradient contribution to both the stabilized `_F` (used by the
198  // strain chain via `_f_inv`) AND the unstabilized `_F_ust` (used by the new F_ust-wrap
199  // architecture in the stress materials). The homogenization gradient is a real
200  // deformation imposed via the scalar constraint, not a stabilization -- it must appear
201  // in F_ust too or PK1 = det(F_ust) sigma F_ust^{-T} will be missing its contribution.
202  for (auto contribution : _homogenization_contributions)
203  {
204  _F[_qp] += (*contribution)[_qp];
205  _F_ust[_qp] += (*contribution)[_qp];
206  }
207 
208  // Publish F_ust^{-1} and det(F_ust) for the large-kinematics consumers' spatial push-forward
209  // (grad_x = F_ust^{-T} grad_X, J_ust = det F_ust) and the Cauchy -> PK1 wrap. Computed once per
210  // qp here -- shared by all displacement kernels and the stress calculator via the material
211  // system -- instead of recomputed per test/trial/qp downstream. Gated on `_large_kinematics`
212  // (every consumer reads these only on the large-kinematics path) and on `isPropertyActive` (so
213  // consumers that never request them pay nothing).
214  if (_large_kinematics && isPropertyActive(_F_ust_inv.id()))
215  {
216  _F_ust_inv[_qp] = _F_ust[_qp].inverse();
217  _F_ust_det[_qp] = _F_ust[_qp].det();
218  }
219 
220  // Skip the Jacobian-only RankFourTensor derivative chain when no downstream consumer
221  // will read it (i.e. we're in a residual-only sweep). All of `_d_F_d_grad_u`,
222  // `_d_deformation_gradient_increment_d_F`, `_d_vorticity_increment_d_F`, and the
223  // `_d_rotation_d_F` slot of the polar decomposition feed only `*_jacobian` material
224  // properties, which the kernel consumes only during Jacobian or
225  // residual-and-Jacobian-together assembly.
226  const bool need_jacobian = _fe_problem.currentlyComputingJacobian() ||
227  _fe_problem.currentlyComputingResidualAndJacobian();
228 
229  // dF/d(grad u_{n+1}) = alpha * I^{(4)} for the generalized midpoint rule
230  // (alpha = 1.0 reduces to backward Euler).
231  if (need_jacobian)
232  _d_F_d_grad_u[_qp] = _alpha * RankFourTensor::IdentityFour();
233 
234  if (_large_kinematics)
235  {
236  _F_inv[_qp] = _F[_qp].inverse();
237  // For `F_bar_mode = incremental`: `_f_inv` must invert the *incremental* F that was
238  // F-bar'd (= `gamma_inc * F_ust * F_ust_old^{-1}`, matching OLD `ComputeFiniteStrain`'s
239  // `_Fhat` after its volumetric-locking correction). Since `_F = gamma_inc * F_ust`,
240  // `(gamma_inc * F_ust * F_ust_old^{-1})^{-1} = F_ust_old * _F^{-1}` -- i.e., pair the
241  // unstabilized old F with the (cumulative) stabilized current `_F^{-1}`. Using
242  // `_F_old` (the *cumulative* F-bar'd previous-step `_F`) here would compound F-bar
243  // across steps and break OLD-compat. `total` mode keeps the existing form (where `_F`
244  // and `_F_old` are the cumulative full-F F-bar'd values and the ratio gives the
245  // F-bar'd incremental F directly).
246  if (_stabilize_strain && _F_bar_mode == FBarMode::Incremental)
247  _f_inv[_qp] = _F_ust_old[_qp] * _F_inv[_qp];
248  else
249  _f_inv[_qp] = _F_old[_qp] * _F_inv[_qp];
250 
251  // Dispatch to the active kinematic-approximation helper. Each helper returns
252  // dd (= Deltad), dw (= Deltaw), and -- only when `need_jacobian` -- the
253  // f^{-1}-derivatives of dL = dd + dw and of dw alone. The (dd, dw) outputs are needed
254  // every iteration; the RankFour derivatives feed only the Jacobian chain below, so on
255  // residual-only sweeps the helper skips them entirely.
256  RankTwoTensor dd, dw;
257  RankFourTensor d_dL_d_f_inv, d_dw_d_f_inv;
258  computeQpLargeKinematicIncrement(
259  _f_inv[_qp], dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
260 
261  if (need_jacobian)
262  {
263  // Common chain rule: d(f^{-1})_{pq}/dF_{mn} = -f^{-1}_{pm} * F^{-1}_{nq}.
264  usingTensorIndices(p_, q_, m_, n_);
265  const RankFourTensor d_f_inv_d_F = -_f_inv[_qp].template times<p_, m_, n_, q_>(_F_inv[_qp]);
266  _d_deformation_gradient_increment_d_F[_qp] = d_dL_d_f_inv * d_f_inv_d_F;
267  _d_vorticity_increment_d_F[_qp] = d_dw_d_f_inv * d_f_inv_d_F;
268  }
269 
270  setQpIncrementalStrains(dd, dw);
271  // The polar decomposition (a tensor eigensolve + sqrt per qp, on every residual and Jacobian
272  // eval) feeds only the Green-Naghdi objective rate's `_rotation` / `_d_rotation_d_F` (and its
273  // internal `_stretch`). Skip it entirely when no active consumer needs it -- Truesdell uses
274  // the deformation-gradient increment, Jaumann the vorticity increment, Rashid the Rodrigues
275  // `_rotation_increment`. The active-property set self-corrects for future consumers with no
276  // coupling flag.
277  if (isPropertyActive(_rotation.id()) || isPropertyActive(_stretch.id()))
278  computeQpPolarDecomposition(need_jacobian);
279  }
280  // For small deformations we just provide the identity (and always linear)
281  else
282  {
283  _F_inv[_qp] = RankTwoTensor::Identity();
284  _f_inv[_qp] = RankTwoTensor::Identity();
285  const RankTwoTensor dL = _F[_qp] - _F_old[_qp];
286 
287  if (need_jacobian)
288  {
289  // d(dL)/dF = I^{(4)} when dL = F - F_old. d(dW)/dF = the skew projector.
290  _d_deformation_gradient_increment_d_F[_qp] = RankFourTensor::IdentityFour();
291  usingTensorIndices(i_, j_, k_, l_);
292  const auto I2 = RankTwoTensor::Identity();
293  _d_vorticity_increment_d_F[_qp] =
294  0.5 * (RankFourTensor::IdentityFour() - I2.template times<j_, k_, i_, l_>(I2));
295 
296  // Small kinematics: R = I, U = I, dR/dF = 0. Defensive defaults; GN is not used here.
297  _d_rotation_d_F[_qp].zero();
298  }
299  _rotation[_qp].setToIdentity();
300  _stretch[_qp].setToIdentity();
301 
302  setQpIncrementalStrains(0.5 * (dL + dL.transpose()), 0.5 * (dL - dL.transpose()));
303  }
304 }
305 
306 template <class G>
307 void
309 {
310  // Polar decomposition F = R * U of the alpha-weighted, F-bar-stabilized deformation
311  // gradient at this qp. We decompose _F rather than _F_actual because the rest of the
312  // kernel/rate chain treats _F as the spatial frame; using _F here matches the pre-3.1
313  // GN rate exactly (it read _def_grad, which is _F via ComputeLagrangianStressCauchy).
314  const RankTwoTensor & F = _F[_qp];
315  FactorizedRankTwoTensor C(F.transpose() * F);
316  // Reuse the sqrt factorization (a tensor eigensolve) for both U and U^{-1}.
317  const auto sqrt_C = MathUtils::sqrt(C);
318  _stretch[_qp] = sqrt_C.get();
319  const RankTwoTensor U_inv = sqrt_C.inverse().get();
320  _rotation[_qp] = F * U_inv;
321 
322  if (!need_jacobian)
323  return;
324 
325  // dR/dF closed form. See ComputeLagrangianObjectiveStress.C:221-227.
326  const auto I = RankTwoTensor::Identity();
327  const RankTwoTensor Y = _stretch[_qp].trace() * I - _stretch[_qp];
328  const RankTwoTensor Z = _rotation[_qp] * Y;
329  const RankTwoTensor O = Z * _rotation[_qp].transpose();
330  usingTensorIndices(i_, j_, k_, l_);
331  _d_rotation_d_F[_qp] =
332  (O.template times<i_, k_, l_, j_>(Y) - Z.template times<i_, l_, k_, j_>(Z)) / Y.det();
333 }
334 
335 template <class G>
336 void
338  RankTwoTensor & dd,
339  RankTwoTensor & dw,
340  RankFourTensor & d_dL_d_f_inv,
341  RankFourTensor & d_dw_d_f_inv,
342  bool need_jacobian)
343 {
344  switch (_kinematic_approximation)
345  {
346  case KinematicApproximation::Linear:
347  computeLinearIncrement(f_inv, dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
348  break;
349  case KinematicApproximation::Quadratic:
350  computeQuadraticIncrement(f_inv, dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
351  break;
352  case KinematicApproximation::RashidApproximate:
353  computeRashidApproximateIncrement(f_inv, dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
354  break;
355  case KinematicApproximation::RashidEigen:
356  computeRashidEigenIncrement(f_inv, dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
357  break;
358  }
359 }
360 
361 template <class G>
362 void
364 {
365  // Backward-compatible entry point: split into sym/skew and delegate.
366  setQpIncrementalStrains(0.5 * (dL + dL.transpose()), 0.5 * (dL - dL.transpose()));
367 }
368 
369 template <class G>
370 void
372  const RankTwoTensor & dw)
373 {
374  _strain_increment[_qp] = dd;
375  _vorticity_increment[_qp] = dw;
376  // Full kinematic spatial velocity gradient increment, before any eigenstrain subtraction.
377  // The objective-rate advection in ComputeLagrangianObjectiveStress consumes this.
378  _deformation_gradient_increment[_qp] = dd + dw;
379 
380  // Increment the total strain
381  _total_strain[_qp] = _total_strain_old[_qp] + _strain_increment[_qp];
382 
383  // Get rid of the eigenstrains
384  // Note we currently do not alter the deformation gradient -- this will be
385  // needed in the future for a "complete" system
386  subtractQpEigenstrainIncrement(_strain_increment[_qp]);
387 
388  // Increment the mechanical strain
389  _mechanical_strain[_qp] = _mechanical_strain_old[_qp] + _strain_increment[_qp];
390 
391  // Additionally maintain the rotated mechanical-strain accumulator,
392  // eps_n+1 = r_hat (eps_n + Deltad) r_hat^T, where r_hat = exp(Deltaw) via Rodrigues. This matches
393  // the mechanical_strain output convention of `ComputeFiniteStrain` and is consumed only by aux
394  // variables -- the stress chain still uses the un-rotated `_mechanical_strain` above. In small
395  // kinematics dw is small and r_hat ~= I + dw + 1/2 dw^2, which contributes only second-order
396  // corrections (equivalent to no rotation for small strain).
397  RankTwoTensor r_hat;
398  if (_large_kinematics)
399  {
400  const Real theta2 = 0.5 * dw.doubleContraction(dw);
401  const Real theta = std::sqrt(theta2);
402  Real f, g;
403  const Real small_theta = 1.0e-7;
404  if (theta < small_theta)
405  {
406  f = 1.0 - theta2 / 6.0;
407  g = 0.5 - theta2 / 24.0;
408  }
409  else
410  {
411  f = std::sin(theta) / theta;
412  g = (1.0 - std::cos(theta)) / theta2;
413  }
414  r_hat = RankTwoTensor::Identity() + f * dw + g * dw * dw;
415  }
416  else
417  {
418  r_hat = RankTwoTensor::Identity();
419  }
420  _rotated_mechanical_strain[_qp] =
421  r_hat * (_rotated_mechanical_strain_old[_qp] + _strain_increment[_qp]) * r_hat.transpose();
422 
423  // Published rotation increment for downstream `ComputeStressBase`-style materials. The
424  // default (identity) preserves the historical Lagrangian pipeline where the objective
425  // rate is the sole rotation source. With `publish_rotation_increment = true` we dispatch
426  // to the `kinematic_approximation`-matched formula so wrapped plasticity with
427  // `perform_finite_strain_rotations = true` rotates its `_stress` bit-for-bit like OLD's
428  // `ComputeFiniteStrain` would have done (the rate then runs in `rotate_old_stress`
429  // passthrough mode so we don't double-rotate). Specifically `rashid_approximate` uses
430  // OLD's C1/C2/C3 polynomial form (not `exp(dw)`) so the wrapped material's accumulated
431  // stress storage matches OLD's `_stress` byte-for-byte through return mapping; without
432  // this, the small (~1e-5) per-step rotation drift between `exp(dw)` and OLD's R_incr
433  // amplifies through plastic flow into ~1e-3 cumulative stress error.
434  _rotation_increment[_qp] = (_publish_rotation_increment && _large_kinematics)
435  ? computeQpRotationIncrement(_f_inv[_qp], dw)
437 }
438 
439 template <class G>
440 void
442  RankTwoTensor & dd,
443  RankTwoTensor & dw,
444  RankFourTensor & d_dL_d_f_inv,
445  RankFourTensor & d_dw_d_f_inv,
446  bool need_jacobian) const
447 {
448  // Deltal = I - f^{-1}, so Deltad = sym(Deltal), Deltaw = skew(Deltal).
449  const RankTwoTensor dL = RankTwoTensor::Identity() - f_inv;
450  dd = 0.5 * (dL + dL.transpose());
451  dw = 0.5 * (dL - dL.transpose());
452 
453  if (!need_jacobian)
454  return;
455  // d(Deltal)/d(f^{-1}) = -I^{(4)}.
456  d_dL_d_f_inv = -RankFourTensor::IdentityFour();
457  // d(Deltaw)/d(f^{-1}) = - skew projector on f^{-1} = -(1/2)(I^{ikjl} - I^{iljk}).
458  usingTensorIndices(i_, j_, m_, n_);
459  const auto I2 = RankTwoTensor::Identity();
460  d_dw_d_f_inv = -0.5 * (RankFourTensor::IdentityFour() - I2.template times<j_, m_, i_, n_>(I2));
461 }
462 
463 template <class G>
464 void
466  RankTwoTensor & dd,
467  RankTwoTensor & dw,
468  RankFourTensor & d_dL_d_f_inv,
469  RankFourTensor & d_dw_d_f_inv,
470  bool need_jacobian) const
471 {
472  // Deltal = X + (1/2) X^2 with X = I - f^{-1} (one more Taylor term of -log f^{-1}).
473  const RankTwoTensor X = RankTwoTensor::Identity() - f_inv;
474  const RankTwoTensor dL = X + 0.5 * X * X;
475  dd = 0.5 * (dL + dL.transpose());
476  dw = 0.5 * (dL - dL.transpose());
477 
478  if (!need_jacobian)
479  return;
480  // dX/d(f^{-1}) = -I^{(4)}, and d(X^2)_{ij}/dX_{mn} = delta_{im} X_{nj} + X_{im} delta_{jn}.
481  // So d(Deltal)/d(f^{-1}) = -I^{(4)} - (1/2) (delta_{im} X_{nj} + X_{im} delta_{jn}).
482  usingTensorIndices(i_, j_, m_, n_);
483  const auto I2 = RankTwoTensor::Identity();
484  const RankFourTensor dXX_dX =
485  I2.template times<i_, m_, n_, j_>(X) + X.template times<i_, m_, j_, n_>(I2);
486  d_dL_d_f_inv = -RankFourTensor::IdentityFour() - 0.5 * dXX_dX;
487  // dw = (1/2)(dL - dL^T), so d(dw)_{ij}/d... = (1/2)(d(dL)_{ij}/d... - d(dL)_{ji}/d...).
488  d_dw_d_f_inv = 0.5 * (d_dL_d_f_inv - d_dL_d_f_inv.transposeIj());
489 }
490 
491 template <class G>
492 void
494  RankTwoTensor & dd,
495  RankTwoTensor & dw,
496  RankFourTensor & d_dL_d_f_inv,
497  RankFourTensor & d_dw_d_f_inv,
498  bool need_jacobian) const
499 {
500  // See plan_outline.pdf Sec.2.3 (eq 10-15, with the corrected vorticity).
501  // X = I - f^{-1}. Symmetric part: A = X X^T - X - X^T, Deltad = -A/2 + A^2/4.
502  // Skew part (from the rotation tensor): alpha_i = eps_ijk (f^{-1})_jk,
503  // cos theta = (tr(f^{-1}) - 1)/2, sin theta = sqrt(1 - cos^2theta), Q = (1/4) alpha*alpha,
504  // Deltaw_ij = -(theta / (2sqrtQ)) eps_ijk alpha_k.
505  usingTensorIndices(i_, j_, m_, n_);
506  const auto I2 = RankTwoTensor::Identity();
507  const auto I4 = RankFourTensor::IdentityFour();
508 
509  // ---- symmetric part ----
510  const RankTwoTensor X = I2 - f_inv;
511  const RankTwoTensor Xt = X.transpose();
512  const RankTwoTensor A = X * Xt - X - Xt;
513  dd = -0.5 * A + 0.25 * A * A;
514 
515  // ---- skew part ----
516  // alpha_i = eps_ijk (f^{-1})_jk (axial vector of f^{-1}'s skew part, doubled).
518  for (unsigned int i = 0; i < 3; ++i)
519  {
520  Real ai = 0.0;
521  for (unsigned int j = 0; j < 3; ++j)
522  for (unsigned int k = 0; k < 3; ++k)
523  ai += PermutationTensor::eps(i, j, k) * f_inv(j, k);
524  alpha(i) = ai;
525  }
526  // Derive theta from the axial vector's magnitude rather than from tr(f^{-1}): the trace
527  // formula assumes f^{-1} is exactly a rotation, which is only true in the limit. Here
528  // sin theta = sqrtQ (Q = |skew part|^2/4); a true rotation matches both, and an arbitrary f^{-1}
529  // is projected onto its nearest "rotation-like" interpretation.
530  const Real Q_raw = 0.25 * (alpha * alpha);
531  // Clamp Q just below 1 so cos theta stays strictly positive (theta -> pi/2 is unphysical for one
532  // step).
533  const Real Q = std::min(Q_raw, 1.0 - 1.0e-12);
534 
535  // Small-angle fallback: when sin theta -> 0, theta/(2sqrtQ) -> 1/2 (L'Hopital) and Deltaw ->
536  // -alpha/2, which matches sym/skew of the linear approximation. d(Deltaw)/d(f^{-1}) is the
537  // antisymmetrizer (1/2)(delta_im delta_jn - delta_jm delta_in).
538  const Real small_Q = 1.0e-12;
539  if (Q < small_Q)
540  {
541  // Deltaw_ij = -(1/2) eps_ijk alpha_k
542  for (unsigned int i = 0; i < 3; ++i)
543  for (unsigned int j = 0; j < 3; ++j)
544  {
545  Real v = 0.0;
546  for (unsigned int k = 0; k < 3; ++k)
547  v += PermutationTensor::eps(i, j, k) * alpha(k);
548  dw(i, j) = -0.5 * v;
549  }
550  // d(Deltaw)/d(f^{-1}) = -(1/2)(delta_{im} delta_{jn} - delta_{jm} delta_{in})
551  if (need_jacobian)
552  d_dw_d_f_inv = -0.5 * (I4 - I2.template times<j_, m_, i_, n_>(I2));
553  }
554  else
555  {
556  const Real sin_theta = std::sqrt(Q);
557  const Real cos_theta = std::sqrt(1.0 - Q);
558  const Real theta = std::asin(sin_theta);
559  const Real coeff = -theta / (2.0 * sin_theta);
560 
561  for (unsigned int i = 0; i < 3; ++i)
562  for (unsigned int j = 0; j < 3; ++j)
563  {
564  Real v = 0.0;
565  for (unsigned int k = 0; k < 3; ++k)
566  v += PermutationTensor::eps(i, j, k) * alpha(k);
567  dw(i, j) = coeff * v;
568  }
569 
570  // Build d(Deltaw)/d(f^{-1}) analytically.
571  // Deltaw_ij = c(f^{-1}) * E_ij(f^{-1}), E_ij = eps_ijk alpha_k, c = -theta/(2 sin theta).
572  //
573  // dalpha_k/d(f^{-1})_{mn} = eps_{kmn} (Levi-Civita is constant).
574  // dQ/d(f^{-1})_{mn} = (1/2) alpha_k eps_{kmn}.
575  // With sin theta = sqrtQ : dsin theta/d(f^{-1})_{mn} = alpha_k eps_{kmn} / (4 sin theta).
576  // dtheta/d(f^{-1})_{mn} = dsin theta/d(f^{-1})_{mn} / cos theta.
577  // dc/dtheta = -(sin theta - theta cos theta)/(2 sin^2 theta); dc/d(f^{-1})_{mn} = dc/dtheta *
578  // dtheta/d(f^{-1})_{mn}
579  // = (theta cos theta - sin theta) alpha_k eps_{kmn} / (8 sin^3 theta cos theta).
580  // dE_ij/d(f^{-1})_{mn} = eps_{ijk} eps_{kmn}.
581  //
582  // Final:
583  // d(Deltaw)_{ij}/d(f^{-1})_{mn} = (dc/d(f^{-1})_{mn}) * E_ij + c * dE_ij/d(f^{-1})_{mn}.
584  if (need_jacobian)
585  {
586  const Real dc_pref =
587  (theta * cos_theta - sin_theta) / (8.0 * sin_theta * sin_theta * sin_theta * cos_theta);
588  for (unsigned int i = 0; i < 3; ++i)
589  for (unsigned int j = 0; j < 3; ++j)
590  {
591  Real E_ij = 0.0;
592  for (unsigned int k = 0; k < 3; ++k)
593  E_ij += PermutationTensor::eps(i, j, k) * alpha(k);
594  for (unsigned int m = 0; m < 3; ++m)
595  for (unsigned int n = 0; n < 3; ++n)
596  {
597  Real eps_alpha = 0.0;
598  for (unsigned int k = 0; k < 3; ++k)
599  eps_alpha += PermutationTensor::eps(k, m, n) * alpha(k);
600  const Real dc_dfinv = dc_pref * eps_alpha;
601  Real dE_dfinv = 0.0;
602  for (unsigned int k = 0; k < 3; ++k)
603  dE_dfinv += PermutationTensor::eps(i, j, k) * PermutationTensor::eps(k, m, n);
604  d_dw_d_f_inv(i, j, m, n) = dc_dfinv * E_ij + coeff * dE_dfinv;
605  }
606  }
607  }
608  }
609 
610  if (!need_jacobian)
611  return;
612 
613  // ---- symmetric-part derivative + assemble d(Deltal)/d(f^{-1}) (Jacobian only) ----
614  // dX/d(f^{-1}) = -I^{(4)}.
615  // d(X X^T)_{ij}/dX_{mn} = delta_{im} X_{jn} + X_{in} delta_{jm} (from (X X^T)_{ij} = X_{ik}
616  // X_{jk}). d(X^T)_{ij}/dX_{mn} = delta_{jm} delta_{in}.
617  // -> d(A)/d(f^{-1}) = -[d(X X^T)/dX] + I^{(4)} + (transposed I^{(4)}).
618  const RankFourTensor d_XXt_dX =
619  I2.template times<i_, m_, j_, n_>(X) + X.template times<i_, n_, j_, m_>(I2);
620  const RankFourTensor d_Xt_dX = I2.template times<j_, m_, i_, n_>(I2);
621  const RankFourTensor dA_dfinv = -d_XXt_dX + I4 + d_Xt_dX;
622 
623  // d(A^2)_{ij}/dA_{mn} = delta_{im} A_{nj} + A_{im} delta_{jn}.
624  const RankFourTensor d_AA_dA =
625  I2.template times<i_, m_, n_, j_>(A) + A.template times<i_, m_, j_, n_>(I2);
626  const RankFourTensor d_AA_dfinv = d_AA_dA * dA_dfinv;
627 
628  const RankFourTensor d_dd_dfinv = -0.5 * dA_dfinv + 0.25 * d_AA_dfinv;
629  d_dL_d_f_inv = d_dd_dfinv + d_dw_d_f_inv;
630 }
631 
632 template <class G>
633 void
635  RankTwoTensor & dd,
636  RankTwoTensor & dw,
637  RankFourTensor & d_dL_d_f_inv,
638  RankFourTensor & d_dw_d_f_inv,
639  bool need_jacobian) const
640 {
641  // See plan_outline.pdf Sec.2.4. Polar-decompose f^{-1} = r' u', with u' symmetric positive
642  // definite and r' a proper rotation. The PDF's identity `log f^{-1} = -log d - log w`
643  // only holds when log u and log r commute (i.e. for non-rotating deformation); in general
644  // the right stretch of f^{-1} is u' = R * U^{-1} * R^T where R, U are the right polar of f.
645  // So -log(u') = R * log U * R^T is the *spatial-frame* log strain, not the co-rotated
646  // log U. We compute it that way first, then rotate by r' = R^T to land in the n-frame
647  // so that _strain_increment = log U exactly. This is the strain measure the Rashid
648  // objective stress update consumes (eq. 22, sigma_{n+1} = r_hat (sigma_n + Deltasigma) r_hat^T
649  // expects Deltasigma in the n-frame).
650  usingTensorIndices(a_, b_, m_, n_);
651  const auto I2 = RankTwoTensor::Identity();
652 
653  // ---- polar decomposition of f^{-1} ----
654  FactorizedRankTwoTensor cprime(f_inv.transpose() * f_inv);
655  // Reuse the sqrt factorization (a tensor eigensolve) for both u and u^{-1}.
656  const auto sqrt_cprime = MathUtils::sqrt(cprime);
657  const RankTwoTensor u = sqrt_cprime.get();
658  const RankTwoTensor u_inv = sqrt_cprime.inverse().get();
659  const RankTwoTensor r = f_inv * u_inv;
660 
661  // ---- intermediate Deltad_spatial = -log u' = R * log U * R^T (n+1 frame) ----
662  // u' = sqrt(c'), so log(u') = (1/2) log(c'). Reuse c''s already-computed factorization
663  // (`sqrt_cprime` shares c''s eigenvectors) instead of re-decomposing u -- one fewer 3x3
664  // eigensolve per qp, on both residual and Jacobian sweeps.
665  const RankTwoTensor dd_spatial = -0.5 * MathUtils::log(cprime).get();
666 
667  // ---- Deltaw = -log r via Rodrigues. ----
668  // log r = phi(theta) (r - r^T) with phi(theta) = theta/(2 sin theta), cos theta = (tr r - 1)/2.
669  const Real cos_theta = MathUtils::clamp(0.5 * (r.trace() - 1.0), -1.0, 1.0);
670  const Real sin2 = std::max(1.0 - cos_theta * cos_theta, 0.0);
671  const Real sin_theta = std::sqrt(sin2);
672  const Real theta = std::acos(cos_theta);
673  const RankTwoTensor A = r - r.transpose();
674 
675  // d(log r)_{ij}/d(r)_{mn} = (dphi/dr_{mn}) A_{ij} + phi (delta_{im} delta_{jn} - delta_{jm}
676  // delta_{in}).
677  // dphi/dr_{mn} = (dphi/dtheta)(dtheta/d cos theta)(d cos theta/dr_{mn})
678  // = psi delta_{mn}, where psi = (theta cos theta - sin theta)/(4 sin^3 theta).
679  // Small-angle: phi -> 1/2, psi -> -1/12, so d(log r)/dr -> (1/2)(I^(4) - swap_ij).
680  // `d_logr_d_r` feeds only the Jacobian, so it is built only when `need_jacobian`.
681  const Real small_sin = 1.0e-7;
682  RankFourTensor d_logr_d_r;
683  RankTwoTensor log_r;
684  usingTensorIndices(i_, j_, k_, l_);
685  if (std::abs(sin_theta) < small_sin)
686  {
687  log_r = 0.5 * A;
688  if (need_jacobian)
689  {
690  const RankFourTensor swap_ij = I2.template times<j_, m_, i_, n_>(I2);
691  d_logr_d_r = 0.5 * (RankFourTensor::IdentityFour() - swap_ij);
692  }
693  }
694  else
695  {
696  const Real phi = theta / (2.0 * sin_theta);
697  log_r = phi * A;
698  if (need_jacobian)
699  {
700  const Real psi = (theta * cos_theta - sin_theta) / (4.0 * sin_theta * sin2);
701  const RankFourTensor swap_ij = I2.template times<j_, m_, i_, n_>(I2);
702  // (dphi/dr)_{mn} = psi delta_{mn} -> outer with A_{ij} gives A.times<i_, j_, m_, n_>(I2) *
703  // psi.
704  const RankFourTensor dphi_outer_A = A.template times<i_, j_, m_, n_>(I2);
705  d_logr_d_r = psi * dphi_outer_A + phi * (RankFourTensor::IdentityFour() - swap_ij);
706  }
707  }
708  dw = -log_r;
709 
710  // ---- Rotate Deltad from spatial back to co-rotated (n) frame: log U = r' * (R log U R^T) *
711  // r'^T, using r' = R^T from the polar of f^{-1}. ----
712  dd = r * dd_spatial * r.transpose();
713 
714  // Everything below is the Jacobian chain (RankFour matrix-log derivatives, the
715  // polar-decomposition dr/d(f^{-1}) closed form, and the three-piece sandwich chain rule).
716  // Skip it wholesale on residual-only sweeps.
717  if (!need_jacobian)
718  return;
719 
720  // d(log u)/d(c') = (1/2) dlog(c'), and d(c')_{ab}/d(f^{-1})_{mn} = delta_{an} f_inv_{mb}
721  // + delta_{bn} f_inv_{ma}.
722  const RankFourTensor dlog_cprime = MathUtils::dlog(cprime);
723  const RankFourTensor d_cprime_d_finv =
724  I2.template times<a_, n_, m_, b_>(f_inv) + I2.template times<b_, n_, m_, a_>(f_inv);
725  const RankFourTensor d_dd_spatial_d_finv = -0.5 * (dlog_cprime * d_cprime_d_finv);
726 
727  // ---- d(r)/d(f^{-1}) via the polar-decomposition closed form (same trick as
728  // ComputeLagrangianObjectiveStress::polarDecomposition). ----
729  const RankTwoTensor Y = u.trace() * I2 - u;
730  const RankTwoTensor Z = r * Y;
731  const RankTwoTensor O = Z * r.transpose();
732  const RankFourTensor d_r_d_finv =
733  (O.template times<i_, k_, l_, j_>(Y) - Z.template times<i_, l_, k_, j_>(Z)) / Y.det();
734 
735  d_dw_d_f_inv = -(d_logr_d_r * d_r_d_finv);
736 
737  // The derivative of the sandwich r'*A*r'^T w.r.t. f^{-1} expands to three rank-4 pieces
738  // (chain rule on r' AND on A). The 6-argument `times<>(RankFourTensor)` overload uses
739  // x[0..4] (one dummy at index 4), so we reuse the same dummy label `p2_` in each
740  // sub-contraction.
741  {
742  usingTensorIndices(i2_, j2_, m2_, n2_, p2_);
743  const RankTwoTensor M = dd_spatial * r.transpose(); // dd * r^T (p, j) shape
744  const RankTwoTensor N = r * dd_spatial; // r * dd (i, q) shape
745  // T1_{ijmn} = (dr/d_finv)_{ip,mn} * M_{pj}
746  const RankFourTensor T1 = M.template times<p2_, j2_, i2_, p2_, m2_, n2_>(d_r_d_finv);
747  // T2_{ijmn} = r_{ip} * (d_dd_spatial_d_finv)_{pq,mn} * r_{jq}: contract twice over the dummy.
748  const RankFourTensor mid = r.template times<i2_, p2_, p2_, j2_, m2_, n2_>(d_dd_spatial_d_finv);
749  const RankFourTensor T2 = r.template times<j2_, p2_, i2_, p2_, m2_, n2_>(mid);
750  // T3_{ijmn} = N_{iq} * (dr/d_finv)_{jq,mn}
751  const RankFourTensor T3 = N.template times<i2_, p2_, j2_, p2_, m2_, n2_>(d_r_d_finv);
752  const RankFourTensor d_dd_d_finv = T1 + T2 + T3;
753  d_dL_d_f_inv = d_dd_d_finv + d_dw_d_f_inv;
754  }
755 }
756 
757 template <class G>
758 void
760 {
761  for (auto i : make_range(_eigenstrain_names.size()))
762  strain -= (*_eigenstrains[i])[_qp] - (*_eigenstrains_old[i])[_qp];
763 }
764 
765 template <class G>
766 void
768 {
769  // Generalized midpoint: F^alpha_{n+1} = I + alpha * (grad u_{n+1}, u_{n+1})
770  // + (1 - alpha) * (grad u_n, u_n).
771  // alpha = 1.0 reduces to backward Euler (no old contribution). With a Steady executioner
772  // the old displacement is treated as identically zero (F_n = I), so we skip the old call
773  // entirely - this lets a user run with alpha != 1 in steady mode as well.
774  _F_ust[_qp].setToIdentity();
775  const bool include_old = _alpha != 1.0 && _fe_problem.isTransient();
776  for (auto component : make_range(_ndisp))
777  {
778  G::addGradOp(_F_ust[_qp],
779  component,
780  _alpha * (*_grad_disp[component])[_qp],
781  _alpha * (*_disp[component])[_qp],
782  _q_point[_qp]);
783  if (include_old)
784  G::addGradOp(_F_ust[_qp],
785  component,
786  (1.0 - _alpha) * (*_grad_disp_old[component])[_qp],
787  (1.0 - _alpha) * (*_disp_old[component])[_qp],
788  _q_point[_qp]);
789  }
790 }
791 
792 template <class G>
793 void
795 {
796  // The literal deformation gradient at n+1 (no alpha weighting, no F-bar). For alpha = 1
797  // this is identical to _F_ust.
798  _F_actual[_qp].setToIdentity();
799  for (auto component : make_range(_ndisp))
800  G::addGradOp(_F_actual[_qp],
801  component,
802  (*_grad_disp[component])[_qp],
803  (*_disp[component])[_qp],
804  _q_point[_qp]);
805 }
806 
807 template <class G>
808 void
810 {
811  // First calculate the unstabilized deformation gradient at each qp
812  for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
813  {
814  computeQpUnstabilizedDeformationGradient();
815  computeQpActualDeformationGradient();
816  _F[_qp] = _F_ust[_qp];
817  }
818 
819  usingTensorIndices(i_, j_, k_, l_);
820  const auto I2 = RankTwoTensor::Identity();
821 
822  // The F-bar local/non-local derivative material properties feed only the Jacobian path
823  // (the stress materials chain them into `_pk1_jacobian`/`_cauchy_jacobian`, and the
824  // TL kernel's non-local F-bar term contracts `_d_F_stab_d_F_avg`). The residual sweep
825  // never reads them, so skip the R4 algebra when assembling a residual alone.
826  const bool need_jacobian = _fe_problem.currentlyComputingJacobian() ||
827  _fe_problem.currentlyComputingResidualAndJacobian();
828 
829  // If stabilization is on do the volumetric correction
830  if (_stabilize_strain)
831  {
832  // `_F_avg` consistently stores the element average of `F_ust` regardless of mode;
833  // it's consumed by the UL kernel for the spatial-frame push-forward, which is a
834  // purely-geometric quantity. In `F_bar_mode = incremental` the F-bar chain itself
835  // operates on the *incremental* averaged tensor `f_avg = avg(F_ust * F_ust_old^{-1})`,
836  // which we compute locally -- the kernel matches by weighting grad_phi by F_ust_old^{-1}
837  // in its element average. Incremental mode only makes sense for large kinematics
838  // (OLD's Fhat F-bar lives in the finite-strain code path).
839  const bool incremental = (_F_bar_mode == FBarMode::Incremental);
840  if (incremental && !_large_kinematics)
841  mooseError("`F_bar_mode = incremental` requires `large_kinematics = true`. The "
842  "incremental F-bar formulation is the multiplicative correction to the "
843  "incremental F, which is only defined for large kinematics. Use "
844  "`F_bar_mode = total` (the default) with small kinematics.");
845  const auto F_avg = StabilizationUtils::elementAverage(
846  [this](unsigned int qp) { return _F_ust[qp]; }, _JxW, _coord);
847  const auto f_avg =
848  incremental
849  ? StabilizationUtils::elementAverage([this](unsigned int qp)
850  { return _F_ust[qp] * _F_ust_old[qp].inverse(); },
851  _JxW,
852  _coord)
853  : RankTwoTensor();
854  // Always publish avg(F_ust) -- UL kernel uses this for the push-forward.
855  _F_avg.set().setAllValues(F_avg);
856  // What the F-bar gamma and `_d_F_stab_d_F_avg` are built against (also what the
857  // kernel-side `_avg_grad_trial` will represent the delta of):
858  const auto & avg_for_chain = incremental ? f_avg : F_avg;
859  // Make the appropriate modification, depending on small or large deformations
860  for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
861  {
862  if (_large_kinematics)
863  {
864  // Multiplicative F-bar: F_stab = gamma * F_ust where
865  // gamma_total = cbrt(det(F_avg) / det(F_ust)) (total mode)
866  // gamma_inc = cbrt(det(f_avg) / det(f_ust)), f_ust = F_ust*F_ust_old^{-1}
867  // (incremental mode)
868  // For incremental: det(f_ust) = det(F_ust)/det(F_ust_old).
869  // d log det(f_ust)/d F_ust = F_ust^{-T} (same as the total-mode chain)
870  // so `_d_F_stab_d_F_ust` shares the total-mode shape. The non-local chain
871  // contracts `_d_F_stab_d_F_avg` with delta(averaged-quantity); kernel computes
872  // deltaf_avg in incremental mode by weighting grad_phi by F_ust_old^{-1} in its
873  // element average.
874  const Real det_ust_local =
875  incremental ? _F_ust[_qp].det() / _F_ust_old[_qp].det() : _F[_qp].det();
876  const Real gamma = std::pow(avg_for_chain.det() / det_ust_local, 1.0 / 3.0);
877  if (need_jacobian)
878  {
879  const auto Fust_invT = _F_ust[_qp].inverse().transpose();
880  const auto avg_invT = avg_for_chain.inverse().transpose();
881  _d_F_stab_d_F_ust[_qp] =
882  gamma * RankFourTensor::IdentityFour() -
883  (gamma / 3.0) * _F_ust[_qp].template times<i_, j_, k_, l_>(Fust_invT);
884  _d_F_stab_d_F_avg[_qp] =
885  (gamma / 3.0) * _F_ust[_qp].template times<i_, j_, k_, l_>(avg_invT);
886  }
887  _F[_qp] *= gamma;
888  }
889  else
890  {
891  if (need_jacobian)
892  {
893  // Additive (trace) F-bar: F_stab = F_ust + (tr(F_avg - F_ust)/3) * I.
894  // dF_stab/dF_ust = I^(4) - (1/3) * I2 (x) I2 (each diagonal component pulled out).
895  // dF_stab/dF_avg = (1/3) * I2 (x) I2.
896  const auto outer = I2.template times<i_, j_, k_, l_>(I2);
897  _d_F_stab_d_F_ust[_qp] = RankFourTensor::IdentityFour() - (1.0 / 3.0) * outer;
898  _d_F_stab_d_F_avg[_qp] = (1.0 / 3.0) * outer;
899  }
900  _F[_qp] += (F_avg.trace() - _F[_qp].trace()) * I2 / 3.0;
901  }
902  }
903  }
904  else if (need_jacobian)
905  {
906  // F-bar off: dF_stab/dF_ust = I^(4), dF_stab/dF_avg = 0.
907  for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
908  {
909  _d_F_stab_d_F_ust[_qp] = RankFourTensor::IdentityFour();
910  _d_F_stab_d_F_avg[_qp].zero();
911  }
912  }
913 }
914 
915 template <class G>
918  const RankTwoTensor & dw) const
919 {
920  // For `rashid_approximate` port OLD `ComputeFiniteStrain`'s C1/C2/C3 polynomial form
921  // exactly (Rashid 1993). Pairing this rotation with the wrapped material's FSR (via
922  // `_rotation_increment`) lets the constitutive's `_stress` evolution match OLD's bit-
923  // for-bit at the same converged displacement state.
924  if (_kinematic_approximation == KinematicApproximation::RashidApproximate)
925  {
926  const Real a[3] = {
927  f_inv(1, 2) - f_inv(2, 1), f_inv(2, 0) - f_inv(0, 2), f_inv(0, 1) - f_inv(1, 0)};
928  const Real q = (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]) / 4.0;
929  const Real trFhatinv_1 = f_inv.trace() - 1.0;
930  const Real p = trFhatinv_1 * trFhatinv_1 / 4.0;
931  const Real C1_squared = p +
932  3.0 * Utility::pow<2>(p) * (1.0 - (p + q)) / Utility::pow<2>(p + q) -
933  2.0 * Utility::pow<3>(p) * (1.0 - (p + q)) / Utility::pow<3>(p + q);
934  if (C1_squared <= 0.0)
935  mooseException(
936  "Cannot take square root of a number less than or equal to zero in the calculation of "
937  "C1 for the Rashid approximation for the rotation tensor.");
938  const Real C1 = std::sqrt(C1_squared);
939  Real C2;
940  if (q > 0.01)
941  C2 = (1.0 - C1) / (4.0 * q);
942  else
943  C2 = 0.125 + q * 0.03125 * (Utility::pow<2>(p) - 12.0 * (p - 1.0)) / Utility::pow<2>(p) +
944  Utility::pow<2>(q) * (p - 2.0) * (Utility::pow<2>(p) - 10.0 * p + 32.0) /
945  Utility::pow<3>(p) +
946  Utility::pow<3>(q) *
947  (1104.0 - 992.0 * p + 376.0 * Utility::pow<2>(p) - 72.0 * Utility::pow<3>(p) +
948  5.0 * Utility::pow<4>(p)) /
949  (512.0 * Utility::pow<4>(p));
950  const Real C3_test =
951  (p * q * (3.0 - q) + Utility::pow<3>(p) + Utility::pow<2>(q)) / Utility::pow<3>(p + q);
952  if (C3_test <= 0.0)
953  mooseException(
954  "Cannot take square root of a number less than or equal to zero in the calculation of "
955  "C3_test for the Rashid approximation for the rotation tensor.");
956  const Real C3 = 0.5 * std::sqrt(C3_test);
957  RankTwoTensor R_incr;
958  R_incr.addIa(C1);
959  for (unsigned int i = 0; i < 3; ++i)
960  for (unsigned int j = 0; j < 3; ++j)
961  R_incr(i, j) += C2 * a[i] * a[j];
962  R_incr(0, 1) += C3 * a[2];
963  R_incr(0, 2) -= C3 * a[1];
964  R_incr(1, 0) -= C3 * a[2];
965  R_incr(1, 2) += C3 * a[0];
966  R_incr(2, 0) += C3 * a[1];
967  R_incr(2, 1) -= C3 * a[0];
968  return R_incr.transpose();
969  }
970 
971  // For `rashid_eigen`, `linear`, `quadratic`: r_hat = exp(dw) via Rodrigues. For RashidEigen,
972  // dw is the matrix log of the polar-decomposition R, so exp(dw) recovers that R bit-for-bit
973  // -- equivalent to OLD `ComputeFiniteStrain`'s EigenSolution rotation.
974  const Real theta2 = 0.5 * dw.doubleContraction(dw);
975  const Real theta = std::sqrt(theta2);
976  Real f, g;
977  const Real small_theta = 1.0e-7;
978  if (theta < small_theta)
979  {
980  f = 1.0 - theta2 / 6.0;
981  g = 0.5 - theta2 / 24.0;
982  }
983  else
984  {
985  f = std::sin(theta) / theta;
986  g = (1.0 - std::cos(theta)) / theta2;
987  }
988  return RankTwoTensor::Identity() + f * dw + g * dw * dw;
989 }
990 
const double M
RankTwoTensorTempl< Real > inverse() const
int eps(unsigned int i, unsigned int j)
FEProblemBase & _fe_problem
std::vector< const VariableGradient * > _grad_disp_old
Old displacement gradients for the generalized midpoint rule.
virtual void computeQpUnstabilizedDeformationGradient()
Calculate the unstabilized (alpha-weighted) deformation gradient at the quadrature point...
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
static RankFourTensorTempl< Real > IdentityFour()
void computeRashidEigenIncrement(const RankTwoTensor &f_inv, RankTwoTensor &dd, RankTwoTensor &dw, RankFourTensor &d_dL_d_f_inv, RankFourTensor &d_dw_d_f_inv, bool need_jacobian) const
"Exact" via polar decomposition of f^{-1} + matrix logs.
void mooseError(Args &&... args)
static const std::string component
Definition: NS.h:157
Add-on class that provides the functionality to issue guarantees for declared material properties...
void issueGuarantee(const MaterialPropertyName &prop_name, Guarantee guarantee)
const double v
static RankTwoTensorTempl Identity()
static const std::string F
Definition: NS.h:169
void suppressParameter(const std::string &name)
std::vector< const MaterialProperty< RankTwoTensor > * > _homogenization_contributions
Actual homogenization contributions.
virtual void computeDeformationGradient()
Calculate the unstabilized and optionally the stabilized deformation gradients.
std::vector< MaterialPropertyName > _homogenization_gradient_names
Names of any extra homogenization gradients.
virtual void computeProperties() override
std::vector< const MaterialProperty< RankTwoTensor > * > _eigenstrains_old
void setQpIncrementalStrains(const RankTwoTensor &dd, const RankTwoTensor &dw)
Update strain / vorticity / mechanical-strain bookkeeping from already-split (dd, dw) tensors...
std::vector< const VariableValue * > _disp_old
Old displacement values for the generalized midpoint rule.
static InputParameters validParams()
Real f(Real x)
Test function for Brents method.
std::vector< const VariableGradient *> coupledGradientsOld(const std::string &var_name) const
std::vector< const MaterialProperty< RankTwoTensor > * > _eigenstrains
void addIa(const Real &a)
Real doubleContraction(const RankTwoTensorTempl< Real > &a) const
std::vector< MaterialPropertyName > _eigenstrain_names
void computeLinearIncrement(const RankTwoTensor &f_inv, RankTwoTensor &dd, RankTwoTensor &dw, RankFourTensor &d_dL_d_f_inv, RankFourTensor &d_dw_d_f_inv, bool need_jacobian) const
Linear approximation: dL = I - f^{-1}.
virtual void initQpStatefulProperties() override
const bool _large_kinematics
If true the equilibrium conditions is calculated with large deformations.
virtual void subtractQpEigenstrainIncrement(RankTwoTensor &strain)
Subtract the eigenstrain increment to subtract from the total strain.
void computeQpPolarDecomposition(bool need_jacobian)
Compute and publish the polar decomposition of _F_actual at the current qp.
void addRequiredCoupledVar(const std::string &name, const std::string &doc_string)
RankTwoTensor computeQpRotationIncrement(const RankTwoTensor &f_inv, const RankTwoTensor &dw) const
Rotation increment matched to the active _kinematic_approximation, suitable for publishing as _rotati...
RankTwoTensorTempl< Real > transpose() const
RankFourTensorTempl< T > transposeIj() const
virtual void computeQpActualDeformationGradient()
Calculate the actual deformation gradient at n+1 (no alpha weighting, no F-bar)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const std::string _base_name
Material system base name.
KinematicApproximation
Approximation used to convert the inverse incremental deformation gradient f^{-1} into the increment ...
int N
static const std::string alpha
Definition: NS.h:138
const Real p
IntRange< T > make_range(T beg, T end)
void computeQuadraticIncrement(const RankTwoTensor &f_inv, RankTwoTensor &dd, RankTwoTensor &dw, RankFourTensor &d_dL_d_f_inv, RankFourTensor &d_dw_d_f_inv, bool need_jacobian) const
Quadratic approximation: dL = (I - f^{-1}) + 0.5 (I - f^{-1})^2.
void mooseError(Args &&... args) const
Calculate strains to use the MOOSE materials with the Lagrangian kernels.
auto elementAverage(const Functor &f, const MooseArray< Real > &JxW, const MooseArray< Real > &coord)
FBarMode
What F gets F-bar volumetric correction applied to.
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
void computeRashidApproximateIncrement(const RankTwoTensor &f_inv, RankTwoTensor &dd, RankTwoTensor &dw, RankFourTensor &d_dL_d_f_inv, RankFourTensor &d_dw_d_f_inv, bool need_jacobian) const
Rashid&#39;s approximate symmetric+skew formulas.
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
virtual void computeQpIncrementalStrains(const RankTwoTensor &dL)
Calculate the strains based on the spatial velocity gradient.
virtual bool isTransient() const override
T clamp(const T &x, T2 lowerlimit, T2 upperlimit)
void computeQpLargeKinematicIncrement(const RankTwoTensor &f_inv, RankTwoTensor &dd, RankTwoTensor &dw, RankFourTensor &d_dL_d_f_inv, RankFourTensor &d_dw_d_f_inv, bool need_jacobian)
Dispatcher: compute (Deltad, Deltaw, d(Deltal)/d(f^{-1}), d(Deltaw)/d(f^{-1})) for the active kinemat...
MooseUnits pow(const MooseUnits &, int)
static const std::string k
Definition: NS.h:134
static const std::string C
Definition: NS.h:172
std::vector< const VariableValue *> coupledValuesOld(const std::string &var_name) const
ComputeLagrangianStrainBase(const InputParameters &parameters)
virtual void computeQpProperties() override