LCOV - code coverage report
Current view: top level - src/materials/lagrangian - ComputeLagrangianStrainBase.C (source / functions) Hit Total Coverage
Test: idaholab/moose solid_mechanics: #33390 (250e9c) with base 846a5c Lines: 404 412 98.1 %
Date: 2026-07-31 18:20:40 Functions: 48 57 84.2 %
Legend: Lines: hit not hit

          Line data    Source code
       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             : 
      10             : #include "ComputeLagrangianStrain.h"
      11             : #include "FactorizedRankTwoTensor.h"
      12             : #include "MathUtils.h"
      13             : #include "PermutationTensor.h"
      14             : 
      15             : template <class G>
      16             : InputParameters
      17        5669 : ComputeLagrangianStrainBase<G>::baseParams()
      18             : {
      19        5669 :   InputParameters params = Material::validParams();
      20             : 
      21       11338 :   params.addRequiredCoupledVar("displacements", "Displacement variables");
      22       11338 :   params.addParam<bool>(
      23       11338 :       "large_kinematics", false, "Use large displacement kinematics in the kernel.");
      24       11338 :   params.addParam<bool>("stabilize_strain", false, "Average the volumetric strains");
      25       11338 :   MooseEnum F_bar_mode("total incremental", "total");
      26       11338 :   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       11338 :   params.addParam<bool>(
      37             :       "publish_rotation_increment",
      38       11338 :       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       17007 :   params.addRangeCheckedParam<Real>(
      47             :       "alpha",
      48       11338 :       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       11338 :   MooseEnum kinematic_approximation("linear quadratic rashid_approximate rashid_eigen", "linear");
      53       11338 :   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       11338 :   params.addParam<std::vector<MaterialPropertyName>>(
      61             :       "eigenstrain_names", {}, "List of eigenstrains to account for");
      62       11338 :   params.addParam<std::vector<MaterialPropertyName>>(
      63             :       "homogenization_gradient_names",
      64             :       {},
      65             :       "List of homogenization gradients to add to the displacement gradient");
      66             : 
      67       11338 :   params.addParam<std::string>("base_name", "Material property base name");
      68             : 
      69             :   // We rely on this *not* having use_displaced mesh on
      70        5669 :   params.suppressParameter<bool>("use_displaced_mesh");
      71             : 
      72        5669 :   return params;
      73        5669 : }
      74             : 
      75             : template <class G>
      76        4251 : ComputeLagrangianStrainBase<G>::ComputeLagrangianStrainBase(const InputParameters & parameters)
      77             :   : Material(parameters),
      78             :     GuaranteeProvider(this),
      79        4251 :     _ndisp(coupledComponents("displacements")),
      80        4251 :     _disp(coupledValues("displacements")),
      81        8502 :     _grad_disp(coupledGradients("displacements")),
      82        8550 :     _base_name(isParamValid("base_name") ? getParam<std::string>("base_name") + "_" : ""),
      83        8502 :     _large_kinematics(getParam<bool>("large_kinematics")),
      84        8502 :     _stabilize_strain(getParam<bool>("stabilize_strain")),
      85        8502 :     _F_bar_mode(getParam<MooseEnum>("F_bar_mode").template getEnum<FBarMode>()),
      86        8502 :     _publish_rotation_increment(getParam<bool>("publish_rotation_increment")),
      87        8502 :     _alpha(getParam<Real>("alpha")),
      88        4251 :     _kinematic_approximation(
      89        4251 :         getParam<MooseEnum>("kinematic_approximation").template getEnum<KinematicApproximation>()),
      90        8502 :     _eigenstrain_names(getParam<std::vector<MaterialPropertyName>>("eigenstrain_names")),
      91        4251 :     _eigenstrains(_eigenstrain_names.size()),
      92        4251 :     _eigenstrains_old(_eigenstrain_names.size()),
      93        4251 :     _total_strain(declareProperty<RankTwoTensor>(_base_name + "total_strain")),
      94        8502 :     _total_strain_old(getMaterialPropertyOld<RankTwoTensor>(_base_name + "total_strain")),
      95        4251 :     _mechanical_strain(declareProperty<RankTwoTensor>(_base_name + "mechanical_strain")),
      96        8502 :     _mechanical_strain_old(getMaterialPropertyOld<RankTwoTensor>(_base_name + "mechanical_strain")),
      97        4251 :     _rotated_mechanical_strain(
      98        4251 :         declareProperty<RankTwoTensor>(_base_name + "rotated_mechanical_strain")),
      99        4251 :     _rotated_mechanical_strain_old(
     100        4251 :         getMaterialPropertyOld<RankTwoTensor>(_base_name + "rotated_mechanical_strain")),
     101        4251 :     _strain_increment(declareProperty<RankTwoTensor>(_base_name + "strain_increment")),
     102        4251 :     _deformation_gradient_increment(
     103        4251 :         declareProperty<RankTwoTensor>(_base_name + "spatial_deformation_gradient_increment")),
     104        4251 :     _vorticity_increment(declareProperty<RankTwoTensor>(_base_name + "vorticity_increment")),
     105        4251 :     _F_ust(declareProperty<RankTwoTensor>(_base_name + "unstabilized_deformation_gradient")),
     106        4251 :     _F_ust_old(
     107        4251 :         getMaterialPropertyOld<RankTwoTensor>(_base_name + "unstabilized_deformation_gradient")),
     108        4251 :     _F_actual(declareProperty<RankTwoTensor>(_base_name + "actual_deformation_gradient")),
     109        4251 :     _F_avg(declareProperty<RankTwoTensor>(_base_name + "average_deformation_gradient")),
     110        4251 :     _F(declareProperty<RankTwoTensor>(_base_name + "deformation_gradient")),
     111        8502 :     _F_old(getMaterialPropertyOld<RankTwoTensor>(_base_name + "deformation_gradient")),
     112        4251 :     _F_inv(declareProperty<RankTwoTensor>(_base_name + "inverse_deformation_gradient")),
     113        4251 :     _f_inv(declareProperty<RankTwoTensor>(_base_name + "inverse_incremental_deformation_gradient")),
     114        4251 :     _F_ust_inv(
     115        4251 :         declareProperty<RankTwoTensor>(_base_name + "inverse_unstabilized_deformation_gradient")),
     116        4251 :     _F_ust_det(declareProperty<Real>(_base_name + "det_unstabilized_deformation_gradient")),
     117        4251 :     _d_deformation_gradient_increment_d_F(declareProperty<RankFourTensor>(
     118             :         _base_name + "d_spatial_deformation_gradient_increment_d_deformation_gradient")),
     119        4251 :     _d_vorticity_increment_d_F(declareProperty<RankFourTensor>(
     120             :         _base_name + "d_vorticity_increment_d_deformation_gradient")),
     121        4251 :     _d_F_d_grad_u(
     122        4251 :         declareProperty<RankFourTensor>(_base_name + "d_deformation_gradient_d_grad_displacement")),
     123        4251 :     _rotation(declareProperty<RankTwoTensor>(_base_name + "rotation")),
     124        4251 :     _stretch(declareProperty<RankTwoTensor>(_base_name + "stretch")),
     125        4251 :     _d_rotation_d_F(
     126        4251 :         declareProperty<RankFourTensor>(_base_name + "d_rotation_d_deformation_gradient")),
     127        4251 :     _d_F_stab_d_F_ust(declareProperty<RankFourTensor>(_base_name + "d_F_stab_d_F_unstabilized")),
     128        4251 :     _d_F_stab_d_F_avg(declareProperty<RankFourTensor>(_base_name + "d_F_stab_d_F_average")),
     129        8502 :     _homogenization_gradient_names(
     130             :         getParam<std::vector<MaterialPropertyName>>("homogenization_gradient_names")),
     131        4251 :     _homogenization_contributions(_homogenization_gradient_names.size()),
     132        8502 :     _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        4251 :   if (_fe_problem.isTransient())
     139             :   {
     140        4155 :     _disp_old = coupledValuesOld("displacements");
     141        8310 :     _grad_disp_old = coupledGradientsOld("displacements");
     142             :   }
     143             : 
     144             :   // Setup eigenstrains
     145        4824 :   for (auto i : make_range(_eigenstrain_names.size()))
     146             :   {
     147         573 :     _eigenstrains[i] = &getMaterialProperty<RankTwoTensor>(_eigenstrain_names[i]);
     148         573 :     _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        4251 :   if (_homogenization_gradient_names.size() > 1)
     153           0 :     mooseError("ComputeLagrangianStrainBase cannot accommodate more than one "
     154             :                "homogenization gradient");
     155             : 
     156             :   // Setup homogenization contributions
     157        4623 :   for (unsigned int i = 0; i < _homogenization_gradient_names.size(); i++)
     158         372 :     _homogenization_contributions[i] =
     159         372 :         &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        4251 :   if (_large_kinematics)
     167        8685 :     issueGuarantee(_base_name + "deformation_gradient", Guarantee::LARGE_KINEMATICS);
     168        4251 : }
     169             : 
     170             : template <class G>
     171             : void
     172      676458 : ComputeLagrangianStrainBase<G>::initQpStatefulProperties()
     173             : {
     174      676458 :   _total_strain[_qp].zero();
     175      676458 :   _mechanical_strain[_qp].zero();
     176      676458 :   _rotated_mechanical_strain[_qp].zero();
     177      676458 :   _F[_qp].setToIdentity();
     178      676458 :   _F_ust[_qp].setToIdentity();
     179      676458 :   _rotation[_qp].setToIdentity();
     180      676458 : }
     181             : 
     182             : template <class G>
     183             : void
     184     6838728 : ComputeLagrangianStrainBase<G>::computeProperties()
     185             : {
     186             :   // Average the volumetric terms, if required
     187     6838728 :   computeDeformationGradient();
     188             : 
     189    60194901 :   for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
     190    53356175 :     computeQpProperties();
     191     6838726 : }
     192             : 
     193             : template <class G>
     194             : void
     195    53356175 : ComputeLagrangianStrainBase<G>::computeQpProperties()
     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    66611395 :   for (auto contribution : _homogenization_contributions)
     203             :   {
     204    13255220 :     _F[_qp] += (*contribution)[_qp];
     205    13255220 :     _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    55483058 :   if (_large_kinematics && isPropertyActive(_F_ust_inv.id()))
     215             :   {
     216    33624342 :     _F_ust_inv[_qp] = _F_ust[_qp].inverse();
     217    33624342 :     _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    53356175 :   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     1813103 :     _d_F_d_grad_u[_qp] = _alpha * RankFourTensor::IdentityFour();
     233             : 
     234    53356175 :   if (_large_kinematics)
     235             :   {
     236    35751225 :     _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    35751225 :     if (_stabilize_strain && _F_bar_mode == FBarMode::Incremental)
     247      917120 :       _f_inv[_qp] = _F_ust_old[_qp] * _F_inv[_qp];
     248             :     else
     249    34834105 :       _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    35751225 :     RankTwoTensor dd, dw;
     257    35751225 :     RankFourTensor d_dL_d_f_inv, d_dw_d_f_inv;
     258    35751225 :     computeQpLargeKinematicIncrement(
     259    35751225 :         _f_inv[_qp], dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
     260             : 
     261    35751223 :     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     1302080 :       const RankFourTensor d_f_inv_d_F = -_f_inv[_qp].template times<p_, m_, n_, q_>(_F_inv[_qp]);
     266     1302080 :       _d_deformation_gradient_increment_d_F[_qp] = d_dL_d_f_inv * d_f_inv_d_F;
     267     1302080 :       _d_vorticity_increment_d_F[_qp] = d_dw_d_f_inv * d_f_inv_d_F;
     268             :     }
     269             : 
     270    35751223 :     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   104094321 :     if (isPropertyActive(_rotation.id()) || isPropertyActive(_stretch.id()))
     278     3159348 :       computeQpPolarDecomposition(need_jacobian);
     279             :   }
     280             :   // For small deformations we just provide the identity (and always linear)
     281             :   else
     282             :   {
     283    17604950 :     _F_inv[_qp] = RankTwoTensor::Identity();
     284    17604950 :     _f_inv[_qp] = RankTwoTensor::Identity();
     285    17604950 :     const RankTwoTensor dL = _F[_qp] - _F_old[_qp];
     286             : 
     287    17604950 :     if (need_jacobian)
     288             :     {
     289             :       // d(dL)/dF = I^{(4)} when dL = F - F_old. d(dW)/dF = the skew projector.
     290      511023 :       _d_deformation_gradient_increment_d_F[_qp] = RankFourTensor::IdentityFour();
     291             :       usingTensorIndices(i_, j_, k_, l_);
     292             :       const auto I2 = RankTwoTensor::Identity();
     293      511023 :       _d_vorticity_increment_d_F[_qp] =
     294     1022046 :           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      511023 :       _d_rotation_d_F[_qp].zero();
     298             :     }
     299    17604950 :     _rotation[_qp].setToIdentity();
     300    17604950 :     _stretch[_qp].setToIdentity();
     301             : 
     302    17604950 :     setQpIncrementalStrains(0.5 * (dL + dL.transpose()), 0.5 * (dL - dL.transpose()));
     303             :   }
     304    53356173 : }
     305             : 
     306             : template <class G>
     307             : void
     308     3159348 : ComputeLagrangianStrainBase<G>::computeQpPolarDecomposition(bool need_jacobian)
     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     3159348 :   const RankTwoTensor & F = _F[_qp];
     315     3159348 :   FactorizedRankTwoTensor C(F.transpose() * F);
     316             :   // Reuse the sqrt factorization (a tensor eigensolve) for both U and U^{-1}.
     317     3159348 :   const auto sqrt_C = MathUtils::sqrt(C);
     318     3159348 :   _stretch[_qp] = sqrt_C.get();
     319     3159348 :   const RankTwoTensor U_inv = sqrt_C.inverse().get();
     320     3159348 :   _rotation[_qp] = F * U_inv;
     321             : 
     322     3159348 :   if (!need_jacobian)
     323             :     return;
     324             : 
     325             :   // dR/dF closed form. See ComputeLagrangianObjectiveStress.C:221-227.
     326             :   const auto I = RankTwoTensor::Identity();
     327       16396 :   const RankTwoTensor Y = _stretch[_qp].trace() * I - _stretch[_qp];
     328       16396 :   const RankTwoTensor Z = _rotation[_qp] * Y;
     329       16396 :   const RankTwoTensor O = Z * _rotation[_qp].transpose();
     330             :   usingTensorIndices(i_, j_, k_, l_);
     331       16396 :   _d_rotation_d_F[_qp] =
     332       32792 :       (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
     337    35751225 : ComputeLagrangianStrainBase<G>::computeQpLargeKinematicIncrement(const RankTwoTensor & f_inv,
     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    35751225 :   switch (_kinematic_approximation)
     345             :   {
     346    21187823 :     case KinematicApproximation::Linear:
     347    21187823 :       computeLinearIncrement(f_inv, dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
     348    21187823 :       break;
     349     4791488 :     case KinematicApproximation::Quadratic:
     350     4791488 :       computeQuadraticIncrement(f_inv, dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
     351     4791488 :       break;
     352     4878784 :     case KinematicApproximation::RashidApproximate:
     353     4878784 :       computeRashidApproximateIncrement(f_inv, dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
     354     4878784 :       break;
     355     4893130 :     case KinematicApproximation::RashidEigen:
     356     4893130 :       computeRashidEigenIncrement(f_inv, dd, dw, d_dL_d_f_inv, d_dw_d_f_inv, need_jacobian);
     357     4893128 :       break;
     358             :   }
     359    35751223 : }
     360             : 
     361             : template <class G>
     362             : void
     363           0 : ComputeLagrangianStrainBase<G>::computeQpIncrementalStrains(const RankTwoTensor & dL)
     364             : {
     365             :   // Backward-compatible entry point: split into sym/skew and delegate.
     366           0 :   setQpIncrementalStrains(0.5 * (dL + dL.transpose()), 0.5 * (dL - dL.transpose()));
     367           0 : }
     368             : 
     369             : template <class G>
     370             : void
     371    53356173 : ComputeLagrangianStrainBase<G>::setQpIncrementalStrains(const RankTwoTensor & dd,
     372             :                                                         const RankTwoTensor & dw)
     373             : {
     374    53356173 :   _strain_increment[_qp] = dd;
     375    53356173 :   _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    53356173 :   _deformation_gradient_increment[_qp] = dd + dw;
     379             : 
     380             :   // Increment the total strain
     381    53356173 :   _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    53356173 :   subtractQpEigenstrainIncrement(_strain_increment[_qp]);
     387             : 
     388             :   // Increment the mechanical strain
     389    53356173 :   _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    53356173 :   RankTwoTensor r_hat;
     398    53356173 :   if (_large_kinematics)
     399             :   {
     400    35751223 :     const Real theta2 = 0.5 * dw.doubleContraction(dw);
     401    35751223 :     const Real theta = std::sqrt(theta2);
     402             :     Real f, g;
     403             :     const Real small_theta = 1.0e-7;
     404    35751223 :     if (theta < small_theta)
     405             :     {
     406     2853513 :       f = 1.0 - theta2 / 6.0;
     407     2853513 :       g = 0.5 - theta2 / 24.0;
     408             :     }
     409             :     else
     410             :     {
     411    32897710 :       f = std::sin(theta) / theta;
     412    32897710 :       g = (1.0 - std::cos(theta)) / theta2;
     413             :     }
     414    35751223 :     r_hat = RankTwoTensor::Identity() + f * dw + g * dw * dw;
     415             :   }
     416             :   else
     417             :   {
     418    17604950 :     r_hat = RankTwoTensor::Identity();
     419             :   }
     420    53356173 :   _rotated_mechanical_strain[_qp] =
     421    53356173 :       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      103168 :   _rotation_increment[_qp] = (_publish_rotation_increment && _large_kinematics)
     435    53459341 :                                  ? computeQpRotationIncrement(_f_inv[_qp], dw)
     436             :                                  : RankTwoTensor::Identity();
     437    53356173 : }
     438             : 
     439             : template <class G>
     440             : void
     441    21187823 : ComputeLagrangianStrainBase<G>::computeLinearIncrement(const RankTwoTensor & f_inv,
     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    21187823 :   const RankTwoTensor dL = RankTwoTensor::Identity() - f_inv;
     450    21187823 :   dd = 0.5 * (dL + dL.transpose());
     451    21187823 :   dw = 0.5 * (dL - dL.transpose());
     452             : 
     453    21187823 :   if (!need_jacobian)
     454             :     return;
     455             :   // d(Deltal)/d(f^{-1}) = -I^{(4)}.
     456     1153792 :   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     3461376 :   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
     465     4791488 : ComputeLagrangianStrainBase<G>::computeQuadraticIncrement(const RankTwoTensor & f_inv,
     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     4791488 :   const RankTwoTensor X = RankTwoTensor::Identity() - f_inv;
     474     4791488 :   const RankTwoTensor dL = X + 0.5 * X * X;
     475     4791488 :   dd = 0.5 * (dL + dL.transpose());
     476     4791488 :   dw = 0.5 * (dL - dL.transpose());
     477             : 
     478     4791488 :   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       28224 :       I2.template times<i_, m_, n_, j_>(X) + X.template times<i_, m_, j_, n_>(I2);
     486       28224 :   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       56448 :   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
     493     4878784 : ComputeLagrangianStrainBase<G>::computeRashidApproximateIncrement(const RankTwoTensor & f_inv,
     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     4878784 :   const RankTwoTensor X = I2 - f_inv;
     511     4878784 :   const RankTwoTensor Xt = X.transpose();
     512     4878784 :   const RankTwoTensor A = X * Xt - X - Xt;
     513     4878784 :   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    19515136 :   for (unsigned int i = 0; i < 3; ++i)
     519             :   {
     520             :     Real ai = 0.0;
     521    58545408 :     for (unsigned int j = 0; j < 3; ++j)
     522   175636224 :       for (unsigned int k = 0; k < 3; ++k)
     523   131727168 :         ai += PermutationTensor::eps(i, j, k) * f_inv(j, k);
     524    14636352 :     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     4878784 :   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     4878784 :   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     4878784 :   if (Q < small_Q)
     540             :   {
     541             :     // Deltaw_ij = -(1/2) eps_ijk alpha_k
     542      267952 :     for (unsigned int i = 0; i < 3; ++i)
     543      803856 :       for (unsigned int j = 0; j < 3; ++j)
     544             :       {
     545             :         Real v = 0.0;
     546     2411568 :         for (unsigned int k = 0; k < 3; ++k)
     547     1808676 :           v += PermutationTensor::eps(i, j, k) * alpha(k);
     548      602892 :         dw(i, j) = -0.5 * v;
     549             :       }
     550             :     // d(Deltaw)/d(f^{-1}) = -(1/2)(delta_{im} delta_{jn} - delta_{jm} delta_{in})
     551       66988 :     if (need_jacobian)
     552       12904 :       d_dw_d_f_inv = -0.5 * (I4 - I2.template times<j_, m_, i_, n_>(I2));
     553             :   }
     554             :   else
     555             :   {
     556     4811796 :     const Real sin_theta = std::sqrt(Q);
     557     4811796 :     const Real cos_theta = std::sqrt(1.0 - Q);
     558     4811796 :     const Real theta = std::asin(sin_theta);
     559     4811796 :     const Real coeff = -theta / (2.0 * sin_theta);
     560             : 
     561    19247184 :     for (unsigned int i = 0; i < 3; ++i)
     562    57741552 :       for (unsigned int j = 0; j < 3; ++j)
     563             :       {
     564             :         Real v = 0.0;
     565   173224656 :         for (unsigned int k = 0; k < 3; ++k)
     566   129918492 :           v += PermutationTensor::eps(i, j, k) * alpha(k);
     567    43306164 :         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     4811796 :     if (need_jacobian)
     585             :     {
     586       53516 :       const Real dc_pref =
     587       53516 :           (theta * cos_theta - sin_theta) / (8.0 * sin_theta * sin_theta * sin_theta * cos_theta);
     588      214064 :       for (unsigned int i = 0; i < 3; ++i)
     589      642192 :         for (unsigned int j = 0; j < 3; ++j)
     590             :         {
     591             :           Real E_ij = 0.0;
     592     1926576 :           for (unsigned int k = 0; k < 3; ++k)
     593     1444932 :             E_ij += PermutationTensor::eps(i, j, k) * alpha(k);
     594     1926576 :           for (unsigned int m = 0; m < 3; ++m)
     595     5779728 :             for (unsigned int n = 0; n < 3; ++n)
     596             :             {
     597             :               Real eps_alpha = 0.0;
     598    17339184 :               for (unsigned int k = 0; k < 3; ++k)
     599    13004388 :                 eps_alpha += PermutationTensor::eps(k, m, n) * alpha(k);
     600     4334796 :               const Real dc_dfinv = dc_pref * eps_alpha;
     601             :               Real dE_dfinv = 0.0;
     602    17339184 :               for (unsigned int k = 0; k < 3; ++k)
     603    13004388 :                 dE_dfinv += PermutationTensor::eps(i, j, k) * PermutationTensor::eps(k, m, n);
     604     4334796 :               d_dw_d_f_inv(i, j, m, n) = dc_dfinv * E_ij + coeff * dE_dfinv;
     605             :             }
     606             :         }
     607             :     }
     608             :   }
     609             : 
     610       59968 :   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       59968 :       I2.template times<i_, m_, j_, n_>(X) + X.template times<i_, n_, j_, m_>(I2);
     620       59968 :   const RankFourTensor d_Xt_dX = I2.template times<j_, m_, i_, n_>(I2);
     621       59968 :   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       59968 :       I2.template times<i_, m_, n_, j_>(A) + A.template times<i_, m_, j_, n_>(I2);
     626       59968 :   const RankFourTensor d_AA_dfinv = d_AA_dA * dA_dfinv;
     627             : 
     628       59968 :   const RankFourTensor d_dd_dfinv = -0.5 * dA_dfinv + 0.25 * d_AA_dfinv;
     629       59968 :   d_dL_d_f_inv = d_dd_dfinv + d_dw_d_f_inv;
     630             : }
     631             : 
     632             : template <class G>
     633             : void
     634     4893130 : ComputeLagrangianStrainBase<G>::computeRashidEigenIncrement(const RankTwoTensor & f_inv,
     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     4893130 :   FactorizedRankTwoTensor cprime(f_inv.transpose() * f_inv);
     655             :   // Reuse the sqrt factorization (a tensor eigensolve) for both u and u^{-1}.
     656     4893128 :   const auto sqrt_cprime = MathUtils::sqrt(cprime);
     657             :   const RankTwoTensor u = sqrt_cprime.get();
     658     4893128 :   const RankTwoTensor u_inv = sqrt_cprime.inverse().get();
     659     4893128 :   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     9786256 :   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     4893128 :   const Real cos_theta = MathUtils::clamp(0.5 * (r.trace() - 1.0), -1.0, 1.0);
     670     4893128 :   const Real sin2 = std::max(1.0 - cos_theta * cos_theta, 0.0);
     671     4893128 :   const Real sin_theta = std::sqrt(sin2);
     672     4893128 :   const Real theta = std::acos(cos_theta);
     673     4893128 :   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     4893128 :   RankFourTensor d_logr_d_r;
     683     4893128 :   RankTwoTensor log_r;
     684             :   usingTensorIndices(i_, j_, k_, l_);
     685     4893128 :   if (std::abs(sin_theta) < small_sin)
     686             :   {
     687       66554 :     log_r = 0.5 * A;
     688       66554 :     if (need_jacobian)
     689             :     {
     690        6438 :       const RankFourTensor swap_ij = I2.template times<j_, m_, i_, n_>(I2);
     691       12876 :       d_logr_d_r = 0.5 * (RankFourTensor::IdentityFour() - swap_ij);
     692             :     }
     693             :   }
     694             :   else
     695             :   {
     696     4826574 :     const Real phi = theta / (2.0 * sin_theta);
     697     4826574 :     log_r = phi * A;
     698     4826574 :     if (need_jacobian)
     699             :     {
     700       53658 :       const Real psi = (theta * cos_theta - sin_theta) / (4.0 * sin_theta * sin2);
     701       53658 :       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       53658 :       const RankFourTensor dphi_outer_A = A.template times<i_, j_, m_, n_>(I2);
     705      107316 :       d_logr_d_r = psi * dphi_outer_A + phi * (RankFourTensor::IdentityFour() - swap_ij);
     706             :     }
     707             :   }
     708     4893128 :   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     4893128 :   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     4893128 :   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       60096 :   const RankFourTensor dlog_cprime = MathUtils::dlog(cprime);
     723             :   const RankFourTensor d_cprime_d_finv =
     724       60096 :       I2.template times<a_, n_, m_, b_>(f_inv) + I2.template times<b_, n_, m_, a_>(f_inv);
     725       60096 :   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       60096 :   const RankTwoTensor Y = u.trace() * I2 - u;
     730       60096 :   const RankTwoTensor Z = r * Y;
     731       60096 :   const RankTwoTensor O = Z * r.transpose();
     732             :   const RankFourTensor d_r_d_finv =
     733       60096 :       (O.template times<i_, k_, l_, j_>(Y) - Z.template times<i_, l_, k_, j_>(Z)) / Y.det();
     734             : 
     735       60096 :   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       60096 :     const RankTwoTensor M = dd_spatial * r.transpose(); // dd * r^T   (p, j) shape
     744       60096 :     const RankTwoTensor N = r * dd_spatial;             // r * dd     (i, q) shape
     745             :     // T1_{ijmn} = (dr/d_finv)_{ip,mn} * M_{pj}
     746       60096 :     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       60096 :     const RankFourTensor mid = r.template times<i2_, p2_, p2_, j2_, m2_, n2_>(d_dd_spatial_d_finv);
     749       60096 :     const RankFourTensor T2 = r.template times<j2_, p2_, i2_, p2_, m2_, n2_>(mid);
     750             :     // T3_{ijmn} = N_{iq} * (dr/d_finv)_{jq,mn}
     751       60096 :     const RankFourTensor T3 = N.template times<i2_, p2_, j2_, p2_, m2_, n2_>(d_r_d_finv);
     752       60096 :     const RankFourTensor d_dd_d_finv = T1 + T2 + T3;
     753       60096 :     d_dL_d_f_inv = d_dd_d_finv + d_dw_d_f_inv;
     754             :   }
     755             : }
     756             : 
     757             : template <class G>
     758             : void
     759    53356173 : ComputeLagrangianStrainBase<G>::subtractQpEigenstrainIncrement(RankTwoTensor & strain)
     760             : {
     761    54017065 :   for (auto i : make_range(_eigenstrain_names.size()))
     762      660892 :     strain -= (*_eigenstrains[i])[_qp] - (*_eigenstrains_old[i])[_qp];
     763    53356173 : }
     764             : 
     765             : template <class G>
     766             : void
     767    53356181 : ComputeLagrangianStrainBase<G>::computeQpUnstabilizedDeformationGradient()
     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    53356181 :   _F_ust[_qp].setToIdentity();
     775    53356181 :   const bool include_old = _alpha != 1.0 && _fe_problem.isTransient();
     776   212132634 :   for (auto component : make_range(_ndisp))
     777             :   {
     778   159508841 :     G::addGradOp(_F_ust[_qp],
     779             :                  component,
     780   158776453 :                  _alpha * (*_grad_disp[component])[_qp],
     781   158776453 :                  _alpha * (*_disp[component])[_qp],
     782      781516 :                  _q_point[_qp]);
     783   158776453 :     if (include_old)
     784    28453848 :       G::addGradOp(_F_ust[_qp],
     785             :                    component,
     786    28473816 :                    (1.0 - _alpha) * (*_grad_disp_old[component])[_qp],
     787    28473816 :                    (1.0 - _alpha) * (*_disp_old[component])[_qp],
     788             :                    _q_point[_qp]);
     789             :   }
     790    53356181 : }
     791             : 
     792             : template <class G>
     793             : void
     794    53356181 : ComputeLagrangianStrainBase<G>::computeQpActualDeformationGradient()
     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    53356181 :   _F_actual[_qp].setToIdentity();
     799   212132634 :   for (auto component : make_range(_ndisp))
     800   158776453 :     G::addGradOp(_F_actual[_qp],
     801             :                  component,
     802             :                  (*_grad_disp[component])[_qp],
     803   158776453 :                  (*_disp[component])[_qp],
     804      781516 :                  _q_point[_qp]);
     805    53356181 : }
     806             : 
     807             : template <class G>
     808             : void
     809     6838728 : ComputeLagrangianStrainBase<G>::computeDeformationGradient()
     810             : {
     811             :   // First calculate the unstabilized deformation gradient at each qp
     812    60194909 :   for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
     813             :   {
     814    53356181 :     computeQpUnstabilizedDeformationGradient();
     815    53356181 :     computeQpActualDeformationGradient();
     816    53356181 :     _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     6838728 :   const bool need_jacobian = _fe_problem.currentlyComputingJacobian() ||
     827             :                              _fe_problem.currentlyComputingResidualAndJacobian();
     828             : 
     829             :   // If stabilization is on do the volumetric correction
     830     6838728 :   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     1865038 :     const bool incremental = (_F_bar_mode == FBarMode::Incremental);
     840     1865038 :     if (incremental && !_large_kinematics)
     841           0 :       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     1865038 :     const auto F_avg = StabilizationUtils::elementAverage(
     846    16482378 :         [this](unsigned int qp) { return _F_ust[qp]; }, _JxW, _coord);
     847     1750398 :     const auto f_avg =
     848             :         incremental
     849     2782158 :             ? StabilizationUtils::elementAverage([this](unsigned int qp)
     850      917120 :                                                  { 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     1865038 :     _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     1865038 :     const auto & avg_for_chain = incremental ? f_avg : F_avg;
     859             :     // Make the appropriate modification, depending on small or large deformations
     860    16482378 :     for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
     861             :     {
     862    14617340 :       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     7829836 :             incremental ? _F_ust[_qp].det() / _F_ust_old[_qp].det() : _F[_qp].det();
     876     7829836 :         const Real gamma = std::pow(avg_for_chain.det() / det_ust_local, 1.0 / 3.0);
     877     7829836 :         if (need_jacobian)
     878             :         {
     879      122328 :           const auto Fust_invT = _F_ust[_qp].inverse().transpose();
     880      122328 :           const auto avg_invT = avg_for_chain.inverse().transpose();
     881      122328 :           _d_F_stab_d_F_ust[_qp] =
     882      122328 :               gamma * RankFourTensor::IdentityFour() -
     883      122328 :               (gamma / 3.0) * _F_ust[_qp].template times<i_, j_, k_, l_>(Fust_invT);
     884      122328 :           _d_F_stab_d_F_avg[_qp] =
     885      244656 :               (gamma / 3.0) * _F_ust[_qp].template times<i_, j_, k_, l_>(avg_invT);
     886             :         }
     887     7829836 :         _F[_qp] *= gamma;
     888             :       }
     889             :       else
     890             :       {
     891     6787504 :         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      168624 :           const auto outer = I2.template times<i_, j_, k_, l_>(I2);
     897      168624 :           _d_F_stab_d_F_ust[_qp] = RankFourTensor::IdentityFour() - (1.0 / 3.0) * outer;
     898      168624 :           _d_F_stab_d_F_avg[_qp] = (1.0 / 3.0) * outer;
     899             :         }
     900     6787504 :         _F[_qp] += (F_avg.trace() - _F[_qp].trace()) * I2 / 3.0;
     901             :       }
     902             :     }
     903             :   }
     904     4973690 :   else if (need_jacobian)
     905             :   {
     906             :     // F-bar off: dF_stab/dF_ust = I^(4), dF_stab/dF_avg = 0.
     907     1736981 :     for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
     908             :     {
     909     1522151 :       _d_F_stab_d_F_ust[_qp] = RankFourTensor::IdentityFour();
     910     1522151 :       _d_F_stab_d_F_avg[_qp].zero();
     911             :     }
     912             :   }
     913     6838728 : }
     914             : 
     915             : template <class G>
     916             : RankTwoTensor
     917      103168 : ComputeLagrangianStrainBase<G>::computeQpRotationIncrement(const RankTwoTensor & f_inv,
     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      103168 :   if (_kinematic_approximation == KinematicApproximation::RashidApproximate)
     925             :   {
     926       51584 :     const Real a[3] = {
     927       51584 :         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       51584 :     const Real q = (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]) / 4.0;
     929       51584 :     const Real trFhatinv_1 = f_inv.trace() - 1.0;
     930       51584 :     const Real p = trFhatinv_1 * trFhatinv_1 / 4.0;
     931       51584 :     const Real C1_squared = p +
     932       51584 :                             3.0 * Utility::pow<2>(p) * (1.0 - (p + q)) / Utility::pow<2>(p + q) -
     933       51584 :                             2.0 * Utility::pow<3>(p) * (1.0 - (p + q)) / Utility::pow<3>(p + q);
     934       51584 :     if (C1_squared <= 0.0)
     935           0 :       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       51584 :     const Real C1 = std::sqrt(C1_squared);
     939             :     Real C2;
     940       51584 :     if (q > 0.01)
     941           0 :       C2 = (1.0 - C1) / (4.0 * q);
     942             :     else
     943       51584 :       C2 = 0.125 + q * 0.03125 * (Utility::pow<2>(p) - 12.0 * (p - 1.0)) / Utility::pow<2>(p) +
     944       51584 :            Utility::pow<2>(q) * (p - 2.0) * (Utility::pow<2>(p) - 10.0 * p + 32.0) /
     945             :                Utility::pow<3>(p) +
     946       51584 :            Utility::pow<3>(q) *
     947       51584 :                (1104.0 - 992.0 * p + 376.0 * Utility::pow<2>(p) - 72.0 * Utility::pow<3>(p) +
     948       51584 :                 5.0 * Utility::pow<4>(p)) /
     949       51584 :                (512.0 * Utility::pow<4>(p));
     950       51584 :     const Real C3_test =
     951       51584 :         (p * q * (3.0 - q) + Utility::pow<3>(p) + Utility::pow<2>(q)) / Utility::pow<3>(p + q);
     952       51584 :     if (C3_test <= 0.0)
     953           0 :       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       51584 :     const Real C3 = 0.5 * std::sqrt(C3_test);
     957       51584 :     RankTwoTensor R_incr;
     958       51584 :     R_incr.addIa(C1);
     959      206336 :     for (unsigned int i = 0; i < 3; ++i)
     960      619008 :       for (unsigned int j = 0; j < 3; ++j)
     961      464256 :         R_incr(i, j) += C2 * a[i] * a[j];
     962       51584 :     R_incr(0, 1) += C3 * a[2];
     963       51584 :     R_incr(0, 2) -= C3 * a[1];
     964       51584 :     R_incr(1, 0) -= C3 * a[2];
     965       51584 :     R_incr(1, 2) += C3 * a[0];
     966       51584 :     R_incr(2, 0) += C3 * a[1];
     967       51584 :     R_incr(2, 1) -= C3 * a[0];
     968       51584 :     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       51584 :   const Real theta2 = 0.5 * dw.doubleContraction(dw);
     975       51584 :   const Real theta = std::sqrt(theta2);
     976             :   Real f, g;
     977             :   const Real small_theta = 1.0e-7;
     978       51584 :   if (theta < small_theta)
     979             :   {
     980        3968 :     f = 1.0 - theta2 / 6.0;
     981        3968 :     g = 0.5 - theta2 / 24.0;
     982             :   }
     983             :   else
     984             :   {
     985       47616 :     f = std::sin(theta) / theta;
     986       47616 :     g = (1.0 - std::cos(theta)) / theta2;
     987             :   }
     988       51584 :   return RankTwoTensor::Identity() + f * dw + g * dw * dw;
     989             : }
     990             : 
     991             : template class ComputeLagrangianStrainBase<GradientOperatorCartesian>;
     992             : template class ComputeLagrangianStrainBase<GradientOperatorAxisymmetricCylindrical>;
     993             : template class ComputeLagrangianStrainBase<GradientOperatorCentrosymmetricSpherical>;

Generated by: LCOV version 1.14