LCOV - code coverage report
Current view: top level - src/materials/lagrangian - LagrangianObjectiveRate.C (source / functions) Hit Total Coverage
Test: idaholab/moose solid_mechanics: #33390 (250e9c) with base 846a5c Lines: 117 118 99.2 %
Date: 2026-07-31 18:20:40 Functions: 11 11 100.0 %
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 "LagrangianObjectiveRate.h"
      11             : #include "MooseEnum.h"
      12             : #include "MooseError.h"
      13             : 
      14             : #include <tuple>
      15             : 
      16             : // ============================================================================
      17             : // File-local helpers (formerly static members of the rate class hierarchy)
      18             : // ============================================================================
      19             : namespace
      20             : {
      21             : /// Build the J tensor that defines the linear advection `sigma_{n+1} = J(dQ)^{-1} sigma`.
      22             : RankFourTensor
      23    30248705 : updateTensor(const RankTwoTensor & dQ)
      24             : {
      25             :   const auto I = RankTwoTensor::Identity();
      26             :   usingTensorIndices(i, j, k, l);
      27    60497410 :   return (1.0 + dQ.trace()) * I.times<i, k, j, l>(I) - dQ.times<i, k, j, l>(I) -
      28    60497410 :          I.times<i, k, j, l>(dQ);
      29             : }
      30             : 
      31             : /// Apply the linear advection to a stress, returning `(advected_stress, Jinv)`.
      32             : std::tuple<RankTwoTensor, RankFourTensor>
      33     1046966 : advectStress(const RankTwoTensor & S0, const RankTwoTensor & dQ)
      34             : {
      35     1046966 :   const RankFourTensor J = updateTensor(dQ);
      36     1046966 :   const RankFourTensor Jinv = J.inverse();
      37     1046966 :   const RankTwoTensor S = Jinv * S0;
      38     1046966 :   return {S, Jinv};
      39             : }
      40             : 
      41             : /// Residual-only advection: solve `J(dQ) : S = S0` for the single stress RHS instead of forming
      42             : /// the full fourth-order inverse. `Jinv` is only needed as an operator on the Jacobian, so on
      43             : /// residual-only sweeps a one-RHS 9x9 solve replaces the 9-column inverse (~2x fewer flops on the
      44             : /// most frequently evaluated path). Correct for any dQ: `J` maps the symmetric subspace to itself,
      45             : /// so the solution equals `Jinv * S0` and stays symmetric even when dQ is not.
      46             : RankTwoTensor
      47    29201739 : advectStressSolve(const RankTwoTensor & S0, const RankTwoTensor & dQ)
      48             : {
      49    29201739 :   const RankFourTensor J = updateTensor(dQ);
      50             :   // Row/col index (a, b) -> (3a + b); A_{(ij)(kl)} = J_ijkl matches RankFourTensor::operator*.
      51             :   Eigen::Matrix<Real, 9, 9> A;
      52             :   Eigen::Matrix<Real, 9, 1> b;
      53   116806956 :   for (const auto i : make_range(3u))
      54   350420868 :     for (const auto j : make_range(3u))
      55             :     {
      56   262815651 :       b(3 * i + j) = S0(i, j);
      57  1051262604 :       for (const auto k : make_range(3u))
      58  3153787812 :         for (const auto l : make_range(3u))
      59  2365340859 :           A(3 * i + j, 3 * k + l) = J(i, j, k, l);
      60             :     }
      61    29201739 :   const Eigen::Matrix<Real, 9, 1> x = A.partialPivLu().solve(b);
      62    29201739 :   RankTwoTensor S;
      63   116806956 :   for (const auto i : make_range(3u))
      64   350420868 :     for (const auto j : make_range(3u))
      65   262815651 :       S(i, j) = x(3 * i + j);
      66    29201739 :   return S;
      67             : }
      68             : 
      69             : /// Derivative of the linear advection action with respect to the kinematic tensor.
      70             : RankFourTensor
      71     1046966 : stressAdvectionDerivative(const RankTwoTensor & S)
      72             : {
      73             :   const auto I = RankTwoTensor::Identity();
      74             :   usingTensorIndices(i, j, k, l);
      75     1046966 :   return S.times<i, j, k, l>(I) - I.times<i, k, l, j>(S) - S.times<i, l, j, k>(I);
      76             : }
      77             : 
      78             : /// Consistent tangent for the linear template.
      79             : RankFourTensor
      80     1046966 : cauchyJacobian(const RankFourTensor & Jinv,
      81             :                const RankFourTensor & small_jacobian,
      82             :                const RankFourTensor & U)
      83             : {
      84     1046966 :   return Jinv * (small_jacobian - U);
      85             : }
      86             : 
      87             : /// Rodrigues exponential of a skew 3x3 tensor W. When `dR_dW` is non-null it is filled with the
      88             : /// unconstrained chain-rule derivative; otherwise the R4 algebra is skipped. The upstream
      89             : /// `d(dW)/dF` projects out non-skew perturbations.
      90             : RankTwoTensor
      91     3333960 : rotationFromVorticity(const RankTwoTensor & W, RankFourTensor * dR_dW)
      92             : {
      93             :   // For a skew W in 3D, theta = sqrt((W : W) / 2). R = exp(W) via Rodrigues:
      94             :   //   R = I + f(theta) W + g(theta) W^2,  f = sin theta / theta,  g = (1 - cos theta) / theta^2.
      95             :   // For theta -> 0, fall back to the Taylor expansion: R ~= I + W + W^2/2.
      96     3333960 :   const Real theta2 = 0.5 * W.doubleContraction(W);
      97     3333960 :   const Real theta = std::sqrt(theta2);
      98             :   const auto I2 = RankTwoTensor::Identity();
      99     3333960 :   const RankTwoTensor W2 = W * W;
     100             : 
     101             :   Real f, g, df_dth, dg_dth;
     102             :   const Real small_theta = 1.0e-7;
     103     3333960 :   if (theta < small_theta)
     104             :   {
     105       29153 :     f = 1.0 - theta2 / 6.0;
     106       29153 :     g = 0.5 - theta2 / 24.0;
     107       29153 :     df_dth = -theta / 3.0;
     108       29153 :     dg_dth = -theta / 12.0;
     109             :   }
     110             :   else
     111             :   {
     112     3304807 :     const Real s = std::sin(theta), c = std::cos(theta);
     113     3304807 :     f = s / theta;
     114     3304807 :     g = (1.0 - c) / theta2;
     115     3304807 :     df_dth = (theta * c - s) / theta2;
     116     3304807 :     dg_dth = (theta * s - 2.0 * (1.0 - c)) / (theta * theta2);
     117             :   }
     118     3333960 :   const RankTwoTensor R = I2 + f * W + g * W2;
     119             : 
     120     3333960 :   if (!dR_dW)
     121             :     return R;
     122             : 
     123             :   // dR_ij/dW_mn = (df/dtheta * dtheta/dW_mn) W_ij + f * delta_im delta_jn
     124             :   //             + (dg/dtheta * dtheta/dW_mn) (W^2)_ij + g * (delta_im W_nj + W_im delta_jn)
     125             :   // dtheta/dW_mn = W_mn / (2theta)   (from d(theta^2)/dW = W).
     126             :   usingTensorIndices(i_, j_, m_, n_);
     127             :   const RankFourTensor d_W2_dW =
     128       79885 :       I2.template times<i_, m_, n_, j_>(W) + W.template times<i_, m_, j_, n_>(I2);
     129       79885 :   *dR_dW = f * RankFourTensor::IdentityFour() + g * d_W2_dW;
     130       79885 :   if (theta >= small_theta)
     131             :   {
     132       71937 :     const Real inv_2theta = 1.0 / (2.0 * theta);
     133             :     // (df/dtheta * W_mn / (2theta)) * W_ij  -> (df/dtheta * inv_2theta) * W (x) W (output indices
     134             :     // ij,mn).
     135      143874 :     *dR_dW += (df_dth * inv_2theta) * W.template times<i_, j_, m_, n_>(W);
     136      143874 :     *dR_dW += (dg_dth * inv_2theta) * W2.template times<i_, j_, m_, n_>(W);
     137             :   }
     138             :   // (For theta < small_theta the dtheta-dependent contributions are O(theta) -> 0; the
     139             :   //  identity + W^2 parts above cover the small-angle limit cleanly.)
     140             : 
     141             :   return R;
     142             : }
     143             : }
     144             : 
     145             : namespace LagrangianObjectiveRates
     146             : {
     147             : 
     148             : // ============================================================================
     149             : // Truesdell
     150             : // ============================================================================
     151             : Outputs
     152    23928368 : truesdell(const Inputs & in, bool need_jacobian)
     153             : {
     154    23928368 :   Outputs out;
     155    23928368 :   if (!need_jacobian)
     156             :   {
     157    22914807 :     out.cauchy_stress = advectStressSolve(in.cauchy_stress_old + in.dS, in.dL);
     158    22914807 :     return out;
     159             :   }
     160             : 
     161     1013561 :   auto [S, Jinv] = advectStress(in.cauchy_stress_old + in.dS, in.dL);
     162     1013561 :   out.cauchy_stress = S;
     163     1013561 :   out.dcauchy_stress_d_eigenstrain = -Jinv * in.small_jacobian;
     164             : 
     165     1013561 :   const RankFourTensor U = stressAdvectionDerivative(S);
     166     1013561 :   out.cauchy_jacobian = cauchyJacobian(Jinv, in.small_jacobian, U);
     167             :   return out;
     168             : }
     169             : 
     170             : // ============================================================================
     171             : // Jaumann
     172             : // ============================================================================
     173             : Outputs
     174     3160988 : jaumann(const Inputs & in, bool need_jacobian)
     175             : {
     176     3160988 :   Outputs out;
     177     3160988 :   if (!need_jacobian)
     178             :   {
     179     3143979 :     out.cauchy_stress = advectStressSolve(in.cauchy_stress_old + in.dS, in.dW);
     180     3143979 :     return out;
     181             :   }
     182             : 
     183       17009 :   auto [S, Jinv] = advectStress(in.cauchy_stress_old + in.dS, in.dW);
     184       17009 :   out.cauchy_stress = S;
     185       17009 :   out.dcauchy_stress_d_eigenstrain = -Jinv * in.small_jacobian;
     186             : 
     187       17009 :   const RankFourTensor d_dW_d_dL = in.d_dW_d_F * in.d_dL_d_F.inverse();
     188       17009 :   const RankFourTensor U = stressAdvectionDerivative(S) * d_dW_d_dL;
     189       17009 :   out.cauchy_jacobian = cauchyJacobian(Jinv, in.small_jacobian, U);
     190             :   return out;
     191             : }
     192             : 
     193             : // ============================================================================
     194             : // Green-Naghdi
     195             : // ============================================================================
     196             : Outputs
     197     3159349 : greenNaghdi(const Inputs & in, bool need_jacobian)
     198             : {
     199             :   usingTensorIndices(i, j, k, l, m);
     200             : 
     201             :   const RankTwoTensor I = RankTwoTensor::Identity();
     202     3159349 :   const RankTwoTensor dR = in.rotation * in.rotation_old.transpose() - I;
     203     3159349 :   const RankTwoTensor dO = dR * in.inv_df;
     204             : 
     205     3159349 :   Outputs out;
     206     3159349 :   if (!need_jacobian)
     207             :   {
     208     3142953 :     out.cauchy_stress = advectStressSolve(in.cauchy_stress_old + in.dS, dO);
     209     3142953 :     return out;
     210             :   }
     211             : 
     212       16396 :   auto [S, Jinv] = advectStress(in.cauchy_stress_old + in.dS, dO);
     213       16396 :   out.cauchy_stress = S;
     214       16396 :   out.dcauchy_stress_d_eigenstrain = -Jinv * in.small_jacobian;
     215             : 
     216       16396 :   const RankFourTensor & d_R_d_F = in.d_rotation_d_F;
     217       16396 :   const RankFourTensor d_F_d_dL = in.d_dL_d_F.inverse();
     218       16396 :   const RankTwoTensor T = in.rotation_old.transpose() * in.inv_df;
     219             : 
     220       16396 :   const RankFourTensor d_invdf_d_F = -in.inv_df.times<i, k, l, j>(in.inv_def_grad);
     221       16396 :   const RankFourTensor d_invdf_d_dL = d_invdf_d_F * d_F_d_dL;
     222       16396 :   const RankFourTensor d_dO_d_invdf = dR.times<i, k, j, l>(I);
     223             : 
     224             :   const RankFourTensor d_dO_d_dL =
     225       16396 :       T.times<m, j, i, m, k, l>(d_R_d_F * d_F_d_dL) + d_dO_d_invdf * d_invdf_d_dL;
     226       16396 :   const RankFourTensor U = stressAdvectionDerivative(S) * d_dO_d_dL;
     227       16396 :   out.cauchy_jacobian = cauchyJacobian(Jinv, in.small_jacobian, U);
     228             :   return out;
     229             : }
     230             : 
     231             : // ============================================================================
     232             : // Rashid
     233             : // ============================================================================
     234             : Outputs
     235     3333960 : rashid(const Inputs & in, bool need_jacobian)
     236             : {
     237             :   usingTensorIndices(i_, j_, k_, l_, m_, n_);
     238             : 
     239             :   // r_hat = exp(Deltaw). Skip its R4 derivative when we don't need the Jacobian.
     240     3333960 :   RankFourTensor d_rhat_d_dW;
     241     6588035 :   const RankTwoTensor rhat = rotationFromVorticity(in.dW, need_jacobian ? &d_rhat_d_dW : nullptr);
     242     3333960 :   const RankTwoTensor rhatT = rhat.transpose();
     243             : 
     244             :   // sigma_{n+1} = r_hat (sigma_n + Deltasigma) r_hat^T   (eq. 22)
     245     3333960 :   const RankTwoTensor S_inner = in.cauchy_stress_old + in.dS;
     246     3333960 :   Outputs out;
     247     3333960 :   out.cauchy_stress = rhat * S_inner * rhatT;
     248             : 
     249     3333960 :   if (!need_jacobian)
     250             :     return out;
     251             : 
     252             :   // J^{-1}_{ijkl} = r_hat_ik r_hat_jl   (eq. 24), the rank-4 (r_hat (x) r_hat) sandwich. Used for
     253             :   // the eigenstrain Jacobian and as the "outer" operator for the constitutive piece. We name the
     254             :   // dummy "middle pair" k, l here (output indices i, j, k, l) -- when chained via
     255             :   // RankFourTensor::operator*, the k, l contract against small_jacobian's first pair, as desired.
     256       79885 :   const RankFourTensor J_inv = rhat.times<i_, k_, j_, l_>(rhat);
     257             : 
     258             :   // dsigma/d(eigenstrain) = -J^{-1} : small_jacobian.
     259             :   // (mechanical_strain = total_strain - eigenstrain, hence the minus.)
     260       79885 :   out.dcauchy_stress_d_eigenstrain = -(J_inv * in.small_jacobian);
     261             : 
     262             :   // Chain rule pieces for dsigma/d(dL):
     263             :   //   d(Deltaw)/d(dL) = d(dW)/dF * inverse(d(dL)/dF)
     264             :   //   d(Deltad)/d(dL) = I^(4) - d(Deltaw)/d(dL)   (Deltad + Deltaw = dL by construction).
     265             :   //   d(r_hat)/d(dL)  = d(r_hat)/d(Deltaw) * d(Deltaw)/d(dL).
     266       79885 :   const RankFourTensor d_F_d_dL = in.d_dL_d_F.inverse();
     267       79885 :   const RankFourTensor d_dW_d_dL = in.d_dW_d_F * d_F_d_dL;
     268       79885 :   const RankFourTensor d_dD_d_dL = RankFourTensor::IdentityFour() - d_dW_d_dL;
     269       79885 :   const RankFourTensor d_rhat_d_dL = d_rhat_d_dW * d_dW_d_dL;
     270             : 
     271             :   // Rotation pieces of dsigma/d(dL):
     272             :   //   T1_{ijkl} = (d_rhat_d_dL)_{imkl} * (S r_hat^T)_{mj}
     273             :   //   T2_{ijkl} = (r_hat S)_{im} * (d_rhat_d_dL)_{jmkl}
     274       79885 :   const RankTwoTensor SR = S_inner * rhatT; // S r_hat^T  (shape (m, j))
     275       79885 :   const RankTwoTensor RS = rhat * S_inner;  // r_hat S    (shape (i, m))
     276       79885 :   const RankFourTensor T1 = SR.times<m_, j_, i_, m_, k_, l_>(d_rhat_d_dL);
     277       79885 :   const RankFourTensor T2 = RS.times<i_, m_, j_, m_, k_, l_>(d_rhat_d_dL);
     278             : 
     279             :   // Constitutive piece: J^{-1} : (small_jacobian * d(Deltad)/d(dL)).
     280       79885 :   const RankFourTensor T3 = J_inv * (in.small_jacobian * d_dD_d_dL);
     281             : 
     282       79885 :   out.cauchy_jacobian = T1 + T2 + T3;
     283             :   return out;
     284             : }
     285             : 
     286             : // ============================================================================
     287             : // Dispatch
     288             : // ============================================================================
     289             : Outputs
     290    33582604 : compute(const MooseEnum & rate, const Inputs & in, bool need_jacobian)
     291             : {
     292             :   const std::string s = rate;
     293    33582604 :   if (s == "truesdell")
     294    23928348 :     return truesdell(in, need_jacobian);
     295     9654256 :   if (s == "jaumann")
     296     3160968 :     return jaumann(in, need_jacobian);
     297     6493288 :   if (s == "green_naghdi")
     298     3159348 :     return greenNaghdi(in, need_jacobian);
     299     3333940 :   if (s == "rashid")
     300     3333940 :     return rashid(in, need_jacobian);
     301           0 :   mooseError("Unknown objective_rate value: ", s);
     302             : }
     303             : }

Generated by: LCOV version 1.14