https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
15template <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
75template <class G>
77 : Material(parameters),
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.
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`.
167 issueGuarantee(_base_name + "deformation_gradient", Guarantee::LARGE_KINEMATICS);
168}
169
170template <class G>
171void
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
182template <class G>
183void
185{
186 // Average the volumetric terms, if required
187 computeDeformationGradient();
188
189 for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
190 computeQpProperties();
191}
192
193template <class G>
194void
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
306template <class G>
307void
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];
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
335template <class G>
336void
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
361template <class G>
362void
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
369template <class G>
370void
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
439template <class G>
440void
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
463template <class G>
464void
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
491template <class G>
492void
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).
517 RealVectorValue alpha;
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
632template <class G>
633void
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
757template <class G>
758void
760{
761 for (auto i : make_range(_eigenstrain_names.size()))
762 strain -= (*_eigenstrains[i])[_qp] - (*_eigenstrains_old[i])[_qp];
763}
764
765template <class G>
766void
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
792template <class G>
793void
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
807template <class G>
808void
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] =
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
915template <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
Real f(Real x)
Test function for Brents method.
const double M
const Real p
const double v
@ LARGE_KINEMATICS
void mooseError(Args &&... args)
Calculate strains to use the MOOSE materials with the Lagrangian kernels.
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...
std::vector< const VariableValue * > _disp_old
Old displacement values for the generalized midpoint rule.
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}.
KinematicApproximation
Approximation used to convert the inverse incremental deformation gradient f^{-1} into the increment ...
std::vector< const MaterialProperty< RankTwoTensor > * > _eigenstrains_old
virtual void computeQpUnstabilizedDeformationGradient()
Calculate the unstabilized (alpha-weighted) deformation gradient at the quadrature point.
virtual void computeQpActualDeformationGradient()
Calculate the actual deformation gradient at n+1 (no alpha weighting, no F-bar)
FBarMode
What F gets F-bar volumetric correction applied to.
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 setQpIncrementalStrains(const RankTwoTensor &dd, const RankTwoTensor &dw)
Update strain / vorticity / mechanical-strain bookkeeping from already-split (dd, dw) tensors.
std::vector< const VariableGradient * > _grad_disp_old
Old displacement gradients for the generalized midpoint rule.
RankTwoTensor computeQpRotationIncrement(const RankTwoTensor &f_inv, const RankTwoTensor &dw) const
Rotation increment matched to the active _kinematic_approximation, suitable for publishing as _rotati...
ComputeLagrangianStrainBase(const InputParameters &parameters)
std::vector< MaterialPropertyName > _homogenization_gradient_names
Names of any extra homogenization gradients.
virtual void computeDeformationGradient()
Calculate the unstabilized and optionally the stabilized deformation gradients.
std::vector< const MaterialProperty< RankTwoTensor > * > _homogenization_contributions
Actual homogenization contributions.
const std::string _base_name
Material system base name.
virtual void subtractQpEigenstrainIncrement(RankTwoTensor &strain)
Subtract the eigenstrain increment to subtract from the total strain.
std::vector< const MaterialProperty< RankTwoTensor > * > _eigenstrains
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's approximate symmetric+skew formulas.
virtual void initQpStatefulProperties() override
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.
virtual void computeQpIncrementalStrains(const RankTwoTensor &dL)
Calculate the strains based on the spatial velocity gradient.
void computeQpPolarDecomposition(bool need_jacobian)
Compute and publish the polar decomposition of _F_actual at the current qp.
std::vector< MaterialPropertyName > _eigenstrain_names
virtual void computeQpProperties() override
const bool _large_kinematics
If true the equilibrium conditions is calculated with large deformations.
std::vector< const VariableGradient * > coupledGradientsOld(const std::string &var_name) const
std::vector< const VariableValue * > coupledValuesOld(const std::string &var_name) const
virtual bool isTransient() const override
Add-on class that provides the functionality to issue guarantees for declared material properties.
void issueGuarantee(const MaterialPropertyName &prop_name, Guarantee guarantee)
void suppressParameter(const std::string &name)
void addRequiredCoupledVar(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 addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
FEProblemBase & _fe_problem
static InputParameters validParams()
void mooseError(Args &&... args) const
RankFourTensorTempl< T > transposeIj() const
static RankFourTensorTempl< T > IdentityFour()
void addIa(const T &a)
T doubleContraction(const RankTwoTensorTempl< T > &a) const
RankTwoTensorTempl< T > inverse() const
RankTwoTensorTempl< T > transpose() const
static RankTwoTensorTempl Identity()
T clamp(const T &x, T2 lowerlimit, T2 upperlimit)
int eps(unsigned int i, unsigned int j)
auto elementAverage(const Functor &f, const MooseArray< Real > &JxW, const MooseArray< Real > &coord)