LCOV - code coverage report
Current view: top level - src/kernels/lagrangian - TotalLagrangianStressDivergenceBase.C (source / functions) Hit Total Coverage
Test: idaholab/moose solid_mechanics: #33390 (250e9c) with base 846a5c Lines: 206 209 98.6 %
Date: 2026-07-31 18:20:40 Functions: 45 63 71.4 %
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 "TotalLagrangianStressDivergenceBase.h"
      11             : 
      12             : template <class G>
      13             : InputParameters
      14        5586 : TotalLagrangianStressDivergenceBase<G>::baseParams()
      15             : {
      16        5586 :   InputParameters params = LagrangianStressDivergenceBase::validParams();
      17             :   // This kernel requires use_displaced_mesh to be off
      18        5586 :   params.suppressParameter<bool>("use_displaced_mesh");
      19        5586 :   return params;
      20           0 : }
      21             : 
      22             : template <class G>
      23        2793 : TotalLagrangianStressDivergenceBase<G>::TotalLagrangianStressDivergenceBase(
      24             :     const InputParameters & parameters)
      25             :   : LagrangianStressDivergenceBase(parameters),
      26        2793 :     _pk1(getMaterialPropertyByName<RankTwoTensor>(_base_name + "pk1_stress")),
      27        5586 :     _dpk1(getMaterialPropertyByName<RankFourTensor>(_base_name + "pk1_jacobian")),
      28        5586 :     _dpk1_d_grad_u(getMaterialPropertyByName<RankFourTensor>(_base_name + "dpk1_d_grad_u")),
      29        2793 :     _dpk1_bypass_fbar(
      30        8379 :         getMaterialPropertyByName<RankFourTensor>(_base_name + "pk1_jacobian_bypass_fbar"))
      31             : {
      32        2793 : }
      33             : 
      34             : template <class G>
      35             : RankTwoTensor
      36  1440805824 : TotalLagrangianStressDivergenceBase<G>::gradTest(unsigned int component)
      37             : {
      38             :   // F-bar doesn't modify the test function
      39  1440805824 :   return G::gradOp(component, _grad_test[_i][_qp], _test[_i][_qp], _q_point[_qp]);
      40             : }
      41             : 
      42             : template <class G>
      43             : RankTwoTensor
      44    23673216 : TotalLagrangianStressDivergenceBase<G>::gradTrial(unsigned int component)
      45             : {
      46             :   // After the F_ust-wrap architectural change, pk1_jacobian = dPK1/d(F_ust) already
      47             :   // contains the local F-bar contribution (via the sigma-chain through
      48             :   // `_d_F_stab_d_F_ust`). The trial gradient is always unstabilized; the non-local F-bar
      49             :   // Jacobian contribution is added explicitly in `computeQpJacobianDisplacement`.
      50    23673216 :   return gradTrialUnstabilized(component);
      51             : }
      52             : 
      53             : template <class G>
      54             : RankTwoTensor
      55    56989152 : TotalLagrangianStressDivergenceBase<G>::gradTrialUnstabilized(unsigned int component)
      56             : {
      57             :   // Without F-bar stabilization, simply return the gradient of the trial functions
      58    56989152 :   return G::gradOp(component, _grad_phi[_j][_qp], _phi[_j][_qp], _q_point[_qp]);
      59             : }
      60             : 
      61             : template <class G>
      62             : void
      63      188471 : TotalLagrangianStressDivergenceBase<G>::precalculateJacobianDisplacement(unsigned int component)
      64             : {
      65             :   // For total Lagrangian, the averaging is taken on the reference frame regardless of geometric
      66             :   // nonlinearity. The stored quantity is what the strain calc's `_d_F_stab_d_F_avg` contracts
      67             :   // with: deltaF_avg in `F_bar_mode = total`, deltaf_avg in `F_bar_mode = incremental`.
      68             :   //   total:        avg_q(grad_phi[j][q])           -> deltaF_avg per DOF
      69             :   //   incremental:  avg_q(grad_phi[j][q] * F_ust_old[q]^{-1})
      70             :   //                                                  -> deltaf_avg per DOF
      71             :   //                 (since f_ust = F_ust * F_ust_old^{-1} and F_ust_old is fixed w.r.t. the
      72             :   //                  current Newton iterate's disp, so deltaf_ust = deltaF_ust * F_ust_old^{-1}).
      73      188471 :   const bool incremental = (_F_bar_mode == FBarMode::Incremental);
      74             :   // F_ust_old[qp]^{-1} depends only on qp, but the average below runs once per trial DOF j.
      75             :   // Precompute the per-qp inverse once instead of re-inverting the 3x3 for every (qp, j).
      76             :   std::vector<RankTwoTensor> f_ust_old_inv;
      77      188471 :   if (incremental)
      78             :   {
      79       60336 :     f_ust_old_inv.resize(_qrule->n_points());
      80      543024 :     for (const auto qp : make_range(_qrule->n_points()))
      81      482688 :       f_ust_old_inv[qp] = _F_ust_old[qp].inverse();
      82             :   }
      83     1593199 :   for (auto j : make_range(_phi.size()))
      84     1404728 :     _avg_grad_trial[component][j] = StabilizationUtils::elementAverage(
      85    12245944 :         [this, component, j, incremental, &f_ust_old_inv](unsigned int qp)
      86             :         {
      87    10841216 :           const RankTwoTensor g = G::gradOp(component, _grad_phi[j][qp], _phi[j][qp], _q_point[qp]);
      88    10841216 :           return incremental ? g * f_ust_old_inv[qp] : g;
      89             :         },
      90             :         _JxW,
      91             :         _coord);
      92      188471 : }
      93             : 
      94             : template <class G>
      95             : void
      96    12515274 : TotalLagrangianStressDivergenceBase<G>::precalculateResidual()
      97             : {
      98             :   LagrangianStressDivergenceBase::precalculateResidual();
      99    12515274 :   if (_stabilize_strain && _F_bar_mode == FBarMode::Incremental)
     100             :   {
     101      314880 :     computeAverageGradientSpatialTest();
     102      314880 :     cachePK1ContractionFUst();
     103             :   }
     104    12515274 : }
     105             : 
     106             : template <class G>
     107             : void
     108      375216 : TotalLagrangianStressDivergenceBase<G>::cachePK1ContractionFUst()
     109             : {
     110      375216 :   const unsigned int n_qp = _qrule->n_points();
     111      375216 :   _pk1_ddot_F_ust.assign(n_qp, 0.0);
     112     3376944 :   for (const auto qp : make_range(n_qp))
     113     3001728 :     _pk1_ddot_F_ust[qp] = _pk1[qp].doubleContraction(_F_ust[qp]);
     114      375216 : }
     115             : 
     116             : template <class G>
     117             : void
     118      478748 : TotalLagrangianStressDivergenceBase<G>::precalculateJacobian()
     119             : {
     120      478748 :   LagrangianStressDivergenceBase::precalculateJacobian();
     121             :   // Populate the per-(qp, j) local PK1 chain cache for the diagonal column (beta = _alpha).
     122             :   // Runs regardless of `_stabilize_strain` because the local chain is always needed; the
     123             :   // helper itself only touches `_avg_grad_trial` when stab is on.
     124      478748 :   populateLocalPK1Cache(_alpha);
     125      478748 :   if (_stabilize_strain && _F_bar_mode == FBarMode::Incremental)
     126             :   {
     127       20112 :     computeAverageGradientSpatialTest();
     128       20112 :     computeAverageGradientSpatialPhi(_alpha);
     129       20112 :     computeAvgTestPhiCross(_alpha);
     130             :   }
     131      478748 : }
     132             : 
     133             : template <class G>
     134             : void
     135      825766 : TotalLagrangianStressDivergenceBase<G>::precalculateOffDiagJacobian(unsigned int jvar)
     136             : {
     137      825766 :   LagrangianStressDivergenceBase::precalculateOffDiagJacobian(jvar);
     138             :   // Populate the per-(qp, j) local PK1 chain cache for the matched off-diagonal beta.
     139     1624108 :   for (auto beta : make_range(_ndisp))
     140     1620002 :     if (jvar == _disp_nums[beta])
     141             :     {
     142      821660 :       populateLocalPK1Cache(beta);
     143      821660 :       if (_stabilize_strain && _F_bar_mode == FBarMode::Incremental)
     144             :       {
     145       40224 :         computeAverageGradientSpatialTest();
     146       40224 :         computeAverageGradientSpatialPhi(beta);
     147       40224 :         computeAvgTestPhiCross(beta);
     148             :       }
     149             :       break;
     150             :     }
     151      825766 : }
     152             : 
     153             : template <class G>
     154             : void
     155     1300408 : TotalLagrangianStressDivergenceBase<G>::populateLocalPK1Cache(unsigned int beta)
     156             : {
     157     1300408 :   const unsigned int n_qp = _qrule->n_points();
     158     1300408 :   const unsigned int n_phi = _phi.size();
     159     1300408 :   _dpk1_grad_trial_cache.assign(n_qp, std::vector<RankTwoTensor>(n_phi));
     160     2600816 :   _grad_trial_cache.assign(n_qp, std::vector<RankTwoTensor>(n_phi));
     161     1300408 :   if (_stabilize_strain)
     162             :   {
     163      364186 :     _delta_PK1_NL_cache.assign(n_qp, std::vector<RankTwoTensor>(n_phi));
     164      364186 :     _dpk1_total_cache.assign(n_qp, std::vector<RankTwoTensor>(n_phi));
     165             :   }
     166             : 
     167             :   // B-bar volumetric correction (incremental mode only): cache the column-independent
     168             :   // `pk1 : F_ust` per qp and the per-(qp, j) `dA/dU` term so the Jacobian inner loop reads
     169             :   // scalars instead of recomputing three double contractions per (_i, _j, component).
     170     1300408 :   const bool bbar = _stabilize_strain && _F_bar_mode == FBarMode::Incremental;
     171             :   if (bbar)
     172             :   {
     173       60336 :     cachePK1ContractionFUst();
     174      120672 :     _dA_dU_cache.assign(n_qp, std::vector<Real>(n_phi));
     175             :   }
     176             : 
     177    11098468 :   for (unsigned int qp = 0; qp < n_qp; ++qp)
     178             :   {
     179     9798060 :     const RankFourTensor & dpk1 = _dpk1_d_grad_u[qp];
     180     9798060 :     const RankFourTensor & dFdGU = _d_F_d_grad_u[qp];
     181    85826264 :     for (unsigned int j = 0; j < n_phi; ++j)
     182             :     {
     183             :       // gradTrial == gradTrialUnstabilized in TL (line ~50); single cache feeds both consumers.
     184    76028204 :       const RankTwoTensor grad_trial = G::gradOp(beta, _grad_phi[j][qp], _phi[j][qp], _q_point[qp]);
     185    76028204 :       _grad_trial_cache[qp][j] = grad_trial;
     186    76028204 :       _dpk1_grad_trial_cache[qp][j] = dpk1 * grad_trial;
     187    76028204 :       if (_stabilize_strain)
     188             :       {
     189             :         // Compose the full non-local F-bar PK1 perturbation:
     190             :         //   deltasigma_NL = D_nl[qp] * (_d_F_d_grad_u[qp] * _avg_grad_trial[beta][j])
     191             :         //   deltaPK1_NL = det(F_ust) * deltasigma_NL * F_ust^{-T}   (large kinematics)
     192             :         //           = deltasigma_NL                              (small kinematics)
     193             :         // D_nl (`_d_nl_fbar`), det(F_ust), and F_ust^{-1} are all per-qp material properties
     194             :         // from the stress/strain materials -- no kernel-side cache.
     195    10656224 :         const RankTwoTensor delta_F_avg = dFdGU * _avg_grad_trial[beta][j];
     196    10656224 :         const RankTwoTensor delta_sigma_nl = (*_d_nl_fbar)[qp] * delta_F_avg;
     197    10656224 :         if (_large_kinematics)
     198     5362112 :           _delta_PK1_NL_cache[qp][j] =
     199     5362112 :               (*_F_ust_det)[qp] * delta_sigma_nl * (*_F_ust_inv)[qp].transpose();
     200             :         else
     201     5294112 :           _delta_PK1_NL_cache[qp][j] = delta_sigma_nl;
     202             :         // Local + non-local PK1 derivative: the single tensor the disp Jacobian contracts against.
     203    10656224 :         _dpk1_total_cache[qp][j] = _dpk1_grad_trial_cache[qp][j] + _delta_PK1_NL_cache[qp][j];
     204             :       }
     205             :       // dA/dU = [ (dPK1_local + dPK1_NL) : F_ust + pk1 : dF_ust ] / 3, all per (qp, j).
     206    76028204 :       if (bbar)
     207     3861504 :         _dA_dU_cache[qp][j] = (_dpk1_total_cache[qp][j].doubleContraction(_F_ust[qp]) +
     208     3861504 :                                _pk1[qp].doubleContraction(_grad_trial_cache[qp][j])) /
     209             :                               3.0;
     210             :     }
     211             :   }
     212     1300408 : }
     213             : 
     214             : template <class G>
     215             : Real
     216    81936384 : TotalLagrangianStressDivergenceBase<G>::gradXTestComponent(unsigned int component) const
     217             : {
     218             :   // (grad_x test_i)_component = (grad_X test_i)_j (F_ust^{-1})_{j, component}
     219             :   // Push-forward via F_ust (= F_actual at alpha=1) matches the PK1 wrap. F_ust^{-1} is a
     220             :   // material property (computed once per qp by the strain calc), not recomputed here.
     221    81936384 :   if (!_large_kinematics)
     222           0 :     return _grad_test[_i][_qp](component);
     223    81936384 :   const RankTwoTensor & F_inv = (*_F_ust_inv)[_qp];
     224             :   Real out = 0.0;
     225   327745536 :   for (unsigned int j = 0; j < 3; ++j)
     226   245809152 :     out += _grad_test[_i][_qp](j) * F_inv(j, component);
     227             :   return out;
     228             : }
     229             : 
     230             : template <class G>
     231             : Real
     232    30892032 : TotalLagrangianStressDivergenceBase<G>::gradXPhiComponent(unsigned int component) const
     233             : {
     234    30892032 :   if (!_large_kinematics)
     235           0 :     return _grad_phi[_j][_qp](component);
     236    30892032 :   const RankTwoTensor & F_inv = (*_F_ust_inv)[_qp];
     237             :   Real out = 0.0;
     238   123568128 :   for (unsigned int j = 0; j < 3; ++j)
     239    92676096 :     out += _grad_phi[_j][_qp](j) * F_inv(j, component);
     240             :   return out;
     241             : }
     242             : 
     243             : template <class G>
     244             : void
     245      375216 : TotalLagrangianStressDivergenceBase<G>::computeAverageGradientSpatialTest()
     246             : {
     247             :   // _avg_grad_spatial_test[i] = (1/V_x) int (grad_x test_i)_alpha dV_x
     248             :   //                           = (sum_qp (grad_X test_i)_j (F_ust^{-1})_{j, _alpha} * J_ust * w)
     249             :   //                             / (sum_qp J_ust * w),  w = JxW * coord
     250             :   // For small kinematics, F_ust = I, J_ust = 1, reducing to OLD's element-averaged grad_test.
     251      375216 :   _avg_grad_spatial_test.assign(_test.size(), 0.0);
     252             :   Real V_x = 0.0;
     253     3376944 :   for (unsigned int qp = 0; qp < _qrule->n_points(); ++qp)
     254             :   {
     255     3001728 :     const Real J = _large_kinematics ? (*_F_ust_det)[qp] : 1.0;
     256     3001728 :     const Real w = J * _JxW[qp] * _coord[qp];
     257     3001728 :     V_x += w;
     258     3001728 :     const RankTwoTensor F_inv = _large_kinematics ? (*_F_ust_inv)[qp] : RankTwoTensor::Identity();
     259    27015552 :     for (unsigned int i = 0; i < _test.size(); ++i)
     260             :     {
     261             :       Real g_x = 0.0;
     262    96055296 :       for (unsigned int j = 0; j < 3; ++j)
     263    72041472 :         g_x += _grad_test[i][qp](j) * F_inv(j, _alpha);
     264    24013824 :       _avg_grad_spatial_test[i] += g_x * w;
     265             :     }
     266             :   }
     267     3376944 :   for (unsigned int i = 0; i < _test.size(); ++i)
     268     3001728 :     _avg_grad_spatial_test[i] /= V_x;
     269      375216 : }
     270             : 
     271             : template <class G>
     272             : void
     273       60336 : TotalLagrangianStressDivergenceBase<G>::computeAverageGradientSpatialPhi(unsigned int beta)
     274             : {
     275             :   // Same structure as computeAverageGradientSpatialTest, but for trial functions and component
     276             :   // beta.
     277       60336 :   _avg_grad_spatial_phi[beta].assign(_phi.size(), 0.0);
     278             :   Real V_x = 0.0;
     279      543024 :   for (unsigned int qp = 0; qp < _qrule->n_points(); ++qp)
     280             :   {
     281      482688 :     const Real J = _large_kinematics ? (*_F_ust_det)[qp] : 1.0;
     282      482688 :     const Real w = J * _JxW[qp] * _coord[qp];
     283      482688 :     V_x += w;
     284      482688 :     const RankTwoTensor F_inv = _large_kinematics ? (*_F_ust_inv)[qp] : RankTwoTensor::Identity();
     285     4344192 :     for (unsigned int j = 0; j < _phi.size(); ++j)
     286             :     {
     287             :       Real g_x = 0.0;
     288    15446016 :       for (unsigned int k = 0; k < 3; ++k)
     289    11584512 :         g_x += _grad_phi[j][qp](k) * F_inv(k, beta);
     290     3861504 :       _avg_grad_spatial_phi[beta][j] += g_x * w;
     291             :     }
     292             :   }
     293      543024 :   for (unsigned int j = 0; j < _phi.size(); ++j)
     294      482688 :     _avg_grad_spatial_phi[beta][j] /= V_x;
     295       60336 : }
     296             : 
     297             : template <class G>
     298             : void
     299       60336 : TotalLagrangianStressDivergenceBase<G>::computeAvgTestPhiCross(unsigned int beta)
     300             : {
     301             :   // Populate the cross-products
     302             :   //   _avg_test_phi_cross[b1][b2][i][j] = (1/V_x) int (grad_x test_i)_{b1} * (grad_x phi_j)_{b2}
     303             :   //   dV_x
     304             :   // for the 4 combinations of (b1, b2) in {_alpha, beta}. The d(avg_grad_spatial_test)/dU
     305             :   // term in the B-bar Jacobian needs both (alpha, beta) and (beta, alpha) entries; the
     306             :   // diagonal (alpha == beta) collapses to a single combination.
     307       60336 :   const unsigned int n_test = _test.size();
     308       60336 :   const unsigned int n_phi = _phi.size();
     309       60336 :   std::vector<unsigned int> bs = {_alpha};
     310       60336 :   if (beta != _alpha)
     311       40224 :     bs.push_back(beta);
     312             :   // Zero out and resize the affected slots
     313      160896 :   for (auto b1 : bs)
     314      281568 :     for (auto b2 : bs)
     315             :     {
     316      181008 :       _avg_test_phi_cross[b1][b2].assign(n_test, std::vector<Real>(n_phi, 0.0));
     317             :     }
     318             :   Real V_x = 0.0;
     319             :   // Cache (grad_x test_i)_{b1} and (grad_x phi_j)_{b2} per qp to avoid recomputing for
     320             :   // each (i, j) pair.
     321       60336 :   std::vector<std::vector<Real>> gx_test(bs.size(), std::vector<Real>(n_test, 0.0));
     322       60336 :   std::vector<std::vector<Real>> gx_phi(bs.size(), std::vector<Real>(n_phi, 0.0));
     323      543024 :   for (unsigned int qp = 0; qp < _qrule->n_points(); ++qp)
     324             :   {
     325      482688 :     const Real J = _large_kinematics ? (*_F_ust_det)[qp] : 1.0;
     326      482688 :     const Real w = J * _JxW[qp] * _coord[qp];
     327      482688 :     V_x += w;
     328      482688 :     const RankTwoTensor F_inv = _large_kinematics ? (*_F_ust_inv)[qp] : RankTwoTensor::Identity();
     329     1287168 :     for (unsigned int bi = 0; bi < bs.size(); ++bi)
     330             :     {
     331      804480 :       const unsigned int b = bs[bi];
     332     7240320 :       for (unsigned int i = 0; i < n_test; ++i)
     333             :       {
     334             :         Real g = 0.0;
     335    25743360 :         for (unsigned int k = 0; k < 3; ++k)
     336    19307520 :           g += _grad_test[i][qp](k) * F_inv(k, b);
     337     6435840 :         gx_test[bi][i] = g;
     338             :       }
     339     7240320 :       for (unsigned int j = 0; j < n_phi; ++j)
     340             :       {
     341             :         Real g = 0.0;
     342    25743360 :         for (unsigned int k = 0; k < 3; ++k)
     343    19307520 :           g += _grad_phi[j][qp](k) * F_inv(k, b);
     344     6435840 :         gx_phi[bi][j] = g;
     345             :       }
     346             :     }
     347     1287168 :     for (unsigned int b1i = 0; b1i < bs.size(); ++b1i)
     348     2252544 :       for (unsigned int b2i = 0; b2i < bs.size(); ++b2i)
     349             :       {
     350     1448064 :         const unsigned int b1 = bs[b1i];
     351     1448064 :         const unsigned int b2 = bs[b2i];
     352     1448064 :         auto & dest = _avg_test_phi_cross[b1][b2];
     353    13032576 :         for (unsigned int i = 0; i < n_test; ++i)
     354   104260608 :           for (unsigned int j = 0; j < n_phi; ++j)
     355    92676096 :             dest[i][j] += gx_test[b1i][i] * gx_phi[b2i][j] * w;
     356             :       }
     357             :   }
     358      160896 :   for (auto b1 : bs)
     359      281568 :     for (auto b2 : bs)
     360             :     {
     361      181008 :       auto & dest = _avg_test_phi_cross[b1][b2];
     362     1629072 :       for (unsigned int i = 0; i < n_test; ++i)
     363    13032576 :         for (unsigned int j = 0; j < n_phi; ++j)
     364    11584512 :           dest[i][j] /= V_x;
     365             :     }
     366       60336 : }
     367             : 
     368             : template <class G>
     369             : Real
     370   784124980 : TotalLagrangianStressDivergenceBase<G>::computeQpResidual()
     371             : {
     372   784124980 :   Real result = gradTest(_alpha).doubleContraction(_pk1[_qp]);
     373             : 
     374             :   // OLD-compat B-bar volumetric correction (only in `F_bar_mode = incremental`, the explicit
     375             :   // OLD-compat mode). This adds
     376             :   //   int (tr sigma / 3) * (avg_grad_x test - grad_x test)_alpha dV_x
     377             :   // expressed in reference-frame integration as
     378             :   //   sum_qp ((PK1 : F_ust) / 3) * (avg_grad_spatial_test[_i] - grad_x_test_alpha) * JxW * coord
     379             :   // (the J = det(F_ust) factor is already absorbed into `PK1 : F_ust = J * tr sigma`).
     380             :   // Without this term, PK1 = det(F_ust) sigma F_ust^{-T} reproduces OLD's `sigma : grad_x test`
     381             :   // term but not OLD's vol-locking correction; for uniform sigma that's harmless (the integral
     382             :   // vanishes per element) but for non-uniform sigma -- e.g. mixed-component Dirichlet BCs producing
     383             :   // non-affine equilibrium with plasticity + a kinematic decomposition that gives spatially varying
     384             :   // sigma -- it drives the converged displacement away from OLD's. Scoped to incremental mode
     385             :   // because (a) that's the documented OLD-compat F-bar mode and (b) the existing `total` mode tests
     386             :   // are calibrated to the F-bar-in-sigma-only formulation (adding the B-bar term there would be
     387             :   // a separate behavior change).
     388   784124980 :   if (_stabilize_strain && _F_bar_mode == FBarMode::Incremental)
     389             :   {
     390    20152320 :     const Real PK1_F_over_3 = _pk1_ddot_F_ust[_qp] / 3.0;
     391    20152320 :     result += PK1_F_over_3 * (_avg_grad_spatial_test[_i] - gradXTestComponent(_alpha));
     392             :   }
     393   784124980 :   return result;
     394             : }
     395             : 
     396             : template <class G>
     397             : Real
     398   598848620 : TotalLagrangianStressDivergenceBase<G>::computeQpJacobianDisplacement(unsigned int alpha,
     399             :                                                                       unsigned int beta)
     400             : {
     401             :   // gradTest(alpha) feeds the local Jacobian contraction below.
     402   598848620 :   const RankTwoTensor grad_test = gradTest(alpha);
     403             : 
     404             :   // Local (+ non-local F-bar) Jacobian: J_{alpha beta} = gradTest_alpha : dPK1_total, where
     405             :   // dPK1_total = dPK1/d(grad u) * grad_phi_beta (local) plus the wrapped non-local F-bar
     406             :   // perturbation. Both pieces depend only on (qp, j) and are precomputed and pre-summed into
     407             :   // `_dpk1_total_cache` by `populateLocalPK1Cache(beta)`, so a single contraction covers both.
     408             :   // Without stabilization there is no non-local piece, so the local cache is contracted directly.
     409  1113750104 :   Real J = grad_test.doubleContraction(_stabilize_strain ? _dpk1_total_cache[_qp][_j]
     410   514901484 :                                                          : _dpk1_grad_trial_cache[_qp][_j]);
     411             : 
     412             :   // OLD-compat B-bar volumetric correction Jacobian (only in `F_bar_mode = incremental`).
     413             :   // The residual added the term R_extra = (PK1:F_ust)/3 * (avg_T - T_alpha) summed over qps.
     414             :   // Linearizing per (i, j, alpha, beta):
     415             :   //   J_extra_qp = (1/3) * d(PK1:F_ust)/dU * (avg_T - T_alpha)
     416             :   //              + (1/3) * (PK1:F_ust) * (d(avg_T)/dU - dT_alpha/dU)
     417             :   // with derivatives in U (the trial-DOF for component beta at node j):
     418             :   //   d(PK1:F_ust)/dU|qp = (dPK1_local + dPK1_NL) : F_ust + PK1 : dF_ust_local
     419             :   //     dPK1_local = _dpk1_grad_trial_cache[_qp][_j]      (cached above)
     420             :   //     dPK1_NL    = d_PK1_NL                             (cached above)
     421             :   //     dF_ust     = _grad_trial_cache[_qp][_j]           (cached unstabilized trial)
     422             :   //   dT_alpha/dU|qp  = -(grad_x test_i)_beta * (grad_x phi_j)_alpha            (local cross)
     423             :   //   d(avg_T)/dU = avgCross(alpha, beta) - avgCross(beta, alpha) - avg_T(i) *
     424             :   //   avg_grad_spatial_phi[beta][j]
     425             :   //     avgCross(b1, b2) = _avg_test_phi_cross[b1][b2][i][j]         (precomputed)
     426   598848620 :   if (_stabilize_strain && _F_bar_mode == FBarMode::Incremental)
     427             :   {
     428             :     // A_qp (= pk1:F_ust/3, per qp) and dA_dU (per qp, j) are precomputed in
     429             :     // `cachePK1ContractionFUst` / `populateLocalPK1Cache` -- no double contractions here.
     430    30892032 :     const Real A_qp = _pk1_ddot_F_ust[_qp] / 3.0;
     431    30892032 :     const Real avg_T = _avg_grad_spatial_test[_i];
     432    30892032 :     const Real T_alpha = gradXTestComponent(alpha);
     433    30892032 :     const Real B = avg_T - T_alpha;
     434             : 
     435    30892032 :     const Real dA_dU = _dA_dU_cache[_qp][_j];
     436             : 
     437             :     // dB/dU: -dT_alpha/dU + d(avg_T)/dU
     438             :     // dT_alpha/dU = -(grad_x test_i)_beta * (grad_x phi_j)_alpha  (local cross at _qp)
     439    30892032 :     const Real T_beta_i = gradXTestComponent(beta);
     440    30892032 :     const Real phi_alpha_j = gradXPhiComponent(alpha);
     441    30892032 :     const Real dT_alpha_dU = -T_beta_i * phi_alpha_j;
     442             : 
     443    30892032 :     const Real avg_cross_ab = _avg_test_phi_cross[alpha][beta][_i][_j];
     444    30892032 :     const Real avg_cross_ba = _avg_test_phi_cross[beta][alpha][_i][_j];
     445    30892032 :     const Real d_avg_T_dU = avg_cross_ab - avg_cross_ba - avg_T * _avg_grad_spatial_phi[beta][_j];
     446             : 
     447    30892032 :     const Real dB_dU = d_avg_T_dU - dT_alpha_dU;
     448    30892032 :     J += dA_dU * B + A_qp * dB_dU;
     449             :   }
     450             : 
     451   598848620 :   return J;
     452             : }
     453             : 
     454             : template <class G>
     455             : Real
     456      843072 : TotalLagrangianStressDivergenceBase<G>::computeQpJacobianTemperature(unsigned int cvar)
     457             : {
     458             :   // Multiple eigenstrains may depend on the same coupled var
     459      843072 :   RankTwoTensor total_deigen;
     460     1686144 :   for (const auto deigen_darg : _deigenstrain_dargs[cvar])
     461      843072 :     total_deigen += (*deigen_darg)[_qp];
     462             : 
     463             :   // No eigenstrain -> no temperature coupling. Short-circuit before dereferencing the
     464             :   // d_sigma/d_eigenstrain property (only fetched when eigenstrains are coupled; see
     465             :   // LagrangianStressDivergenceBase ctor).
     466      843072 :   if (total_deigen.L2norm() == 0.0)
     467             :     return 0.0;
     468             : 
     469             :   // Direct chain through the constitutive update. The stress material publishes
     470             :   //   d_sigma/d_eigenstrain   (= -Jinv * small_jacobian for the objective-rate path,
     471             :   //                            with the sign convention that an eigenstrain increase
     472             :   //                            reduces the mechanical strain).
     473             :   // We then wrap to PK1 the same way the residual does:
     474             :   //   dP/dT = det(F) * dsigma/dT * F^{-T}   (large kinematics)
     475             :   //   dP/dT = dsigma/dT                     (small kinematics, P == sigma by convention)
     476      843072 :   const RankTwoTensor dsigma_dT = (*_dcauchy_stress_d_eigenstrain)[_qp] * total_deigen;
     477      843072 :   RankTwoTensor dP_dT;
     478      843072 :   if (_large_kinematics)
     479      791808 :     dP_dT = _F[_qp].det() * dsigma_dT * _F_inv[_qp].transpose();
     480             :   else
     481       51264 :     dP_dT = dsigma_dT;
     482      843072 :   return dP_dT.doubleContraction(gradTest(_alpha)) * _temperature->phi()[_j][_qp];
     483             : }
     484             : 
     485             : template <class G>
     486             : Real
     487       85888 : TotalLagrangianStressDivergenceBase<G>::computeQpJacobianOutOfPlaneStrain()
     488             : {
     489             :   // d(R_disp_alpha)/d(strain_zz_j) at qp = gradTest_alpha : d(PK1)/d(strain_zz_j).
     490             :   // strain_zz feeds `_F[(2,2)]` AFTER F-bar runs in `ComputeLagrangianWPSStrain`, so
     491             :   // strain_zz perturbations bypass F-bar's chain. Use `_dpk1_bypass_fbar` (= the
     492             :   // pk1_jacobian variant computed with the F-bar `_d_F_stab_d_F_ust` factor REPLACED
     493             :   // by identity in the sigma chain) for a consistent Jacobian.
     494       85888 :   return _dpk1_bypass_fbar[_qp].contractionKl(2, 2, gradTest(_alpha)) *
     495       85888 :          _out_of_plane_strain->phi()[_j][_qp];
     496             : }
     497             : 
     498             : template class TotalLagrangianStressDivergenceBase<GradientOperatorCartesian>;
     499             : template class TotalLagrangianStressDivergenceBase<GradientOperatorAxisymmetricCylindrical>;
     500             : template class TotalLagrangianStressDivergenceBase<GradientOperatorCentrosymmetricSpherical>;

Generated by: LCOV version 1.14