https://mooseframework.inl.gov
LagrangianObjectiveRate.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
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 {
23 updateTensor(const RankTwoTensor & dQ)
24 {
25  const auto I = RankTwoTensor::Identity();
26  usingTensorIndices(i, j, k, l);
27  return (1.0 + dQ.trace()) * I.times<i, k, j, l>(I) - dQ.times<i, k, j, l>(I) -
28  I.times<i, k, j, l>(dQ);
29 }
30 
32 std::tuple<RankTwoTensor, RankFourTensor>
33 advectStress(const RankTwoTensor & S0, const RankTwoTensor & dQ)
34 {
35  const RankFourTensor J = updateTensor(dQ);
36  const RankFourTensor Jinv = J.inverse();
37  const RankTwoTensor S = Jinv * S0;
38  return {S, Jinv};
39 }
40 
47 advectStressSolve(const RankTwoTensor & S0, const RankTwoTensor & dQ)
48 {
49  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  for (const auto i : make_range(3u))
54  for (const auto j : make_range(3u))
55  {
56  b(3 * i + j) = S0(i, j);
57  for (const auto k : make_range(3u))
58  for (const auto l : make_range(3u))
59  A(3 * i + j, 3 * k + l) = J(i, j, k, l);
60  }
61  const Eigen::Matrix<Real, 9, 1> x = A.partialPivLu().solve(b);
63  for (const auto i : make_range(3u))
64  for (const auto j : make_range(3u))
65  S(i, j) = x(3 * i + j);
66  return S;
67 }
68 
71 stressAdvectionDerivative(const RankTwoTensor & S)
72 {
73  const auto I = RankTwoTensor::Identity();
74  usingTensorIndices(i, j, k, l);
75  return S.times<i, j, k, l>(I) - I.times<i, k, l, j>(S) - S.times<i, l, j, k>(I);
76 }
77 
80 cauchyJacobian(const RankFourTensor & Jinv,
81  const RankFourTensor & small_jacobian,
82  const RankFourTensor & U)
83 {
84  return Jinv * (small_jacobian - U);
85 }
86 
91 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  const Real theta2 = 0.5 * W.doubleContraction(W);
97  const Real theta = std::sqrt(theta2);
98  const auto I2 = RankTwoTensor::Identity();
99  const RankTwoTensor W2 = W * W;
100 
101  Real f, g, df_dth, dg_dth;
102  const Real small_theta = 1.0e-7;
103  if (theta < small_theta)
104  {
105  f = 1.0 - theta2 / 6.0;
106  g = 0.5 - theta2 / 24.0;
107  df_dth = -theta / 3.0;
108  dg_dth = -theta / 12.0;
109  }
110  else
111  {
112  const Real s = std::sin(theta), c = std::cos(theta);
113  f = s / theta;
114  g = (1.0 - c) / theta2;
115  df_dth = (theta * c - s) / theta2;
116  dg_dth = (theta * s - 2.0 * (1.0 - c)) / (theta * theta2);
117  }
118  const RankTwoTensor R = I2 + f * W + g * W2;
119 
120  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  I2.template times<i_, m_, n_, j_>(W) + W.template times<i_, m_, j_, n_>(I2);
129  *dR_dW = f * RankFourTensor::IdentityFour() + g * d_W2_dW;
130  if (theta >= small_theta)
131  {
132  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  *dR_dW += (df_dth * inv_2theta) * W.template times<i_, j_, m_, n_>(W);
136  *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 truesdell(const Inputs & in, bool need_jacobian)
153 {
154  Outputs out;
155  if (!need_jacobian)
156  {
157  out.cauchy_stress = advectStressSolve(in.cauchy_stress_old + in.dS, in.dL);
158  return out;
159  }
160 
161  auto [S, Jinv] = advectStress(in.cauchy_stress_old + in.dS, in.dL);
162  out.cauchy_stress = S;
163  out.dcauchy_stress_d_eigenstrain = -Jinv * in.small_jacobian;
164 
165  const RankFourTensor U = stressAdvectionDerivative(S);
166  out.cauchy_jacobian = cauchyJacobian(Jinv, in.small_jacobian, U);
167  return out;
168 }
169 
170 // ============================================================================
171 // Jaumann
172 // ============================================================================
173 Outputs
174 jaumann(const Inputs & in, bool need_jacobian)
175 {
176  Outputs out;
177  if (!need_jacobian)
178  {
179  out.cauchy_stress = advectStressSolve(in.cauchy_stress_old + in.dS, in.dW);
180  return out;
181  }
182 
183  auto [S, Jinv] = advectStress(in.cauchy_stress_old + in.dS, in.dW);
184  out.cauchy_stress = S;
185  out.dcauchy_stress_d_eigenstrain = -Jinv * in.small_jacobian;
186 
187  const RankFourTensor d_dW_d_dL = in.d_dW_d_F * in.d_dL_d_F.inverse();
188  const RankFourTensor U = stressAdvectionDerivative(S) * d_dW_d_dL;
189  out.cauchy_jacobian = cauchyJacobian(Jinv, in.small_jacobian, U);
190  return out;
191 }
192 
193 // ============================================================================
194 // Green-Naghdi
195 // ============================================================================
196 Outputs
197 greenNaghdi(const Inputs & in, bool need_jacobian)
198 {
199  usingTensorIndices(i, j, k, l, m);
200 
202  const RankTwoTensor dR = in.rotation * in.rotation_old.transpose() - I;
203  const RankTwoTensor dO = dR * in.inv_df;
204 
205  Outputs out;
206  if (!need_jacobian)
207  {
208  out.cauchy_stress = advectStressSolve(in.cauchy_stress_old + in.dS, dO);
209  return out;
210  }
211 
212  auto [S, Jinv] = advectStress(in.cauchy_stress_old + in.dS, dO);
213  out.cauchy_stress = S;
214  out.dcauchy_stress_d_eigenstrain = -Jinv * in.small_jacobian;
215 
216  const RankFourTensor & d_R_d_F = in.d_rotation_d_F;
217  const RankFourTensor d_F_d_dL = in.d_dL_d_F.inverse();
218  const RankTwoTensor T = in.rotation_old.transpose() * in.inv_df;
219 
220  const RankFourTensor d_invdf_d_F = -in.inv_df.times<i, k, l, j>(in.inv_def_grad);
221  const RankFourTensor d_invdf_d_dL = d_invdf_d_F * d_F_d_dL;
222  const RankFourTensor d_dO_d_invdf = dR.times<i, k, j, l>(I);
223 
224  const RankFourTensor d_dO_d_dL =
225  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  const RankFourTensor U = stressAdvectionDerivative(S) * d_dO_d_dL;
227  out.cauchy_jacobian = cauchyJacobian(Jinv, in.small_jacobian, U);
228  return out;
229 }
230 
231 // ============================================================================
232 // Rashid
233 // ============================================================================
234 Outputs
235 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  RankFourTensor d_rhat_d_dW;
241  const RankTwoTensor rhat = rotationFromVorticity(in.dW, need_jacobian ? &d_rhat_d_dW : nullptr);
242  const RankTwoTensor rhatT = rhat.transpose();
243 
244  // sigma_{n+1} = r_hat (sigma_n + Deltasigma) r_hat^T (eq. 22)
245  const RankTwoTensor S_inner = in.cauchy_stress_old + in.dS;
246  Outputs out;
247  out.cauchy_stress = rhat * S_inner * rhatT;
248 
249  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  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  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  const RankFourTensor d_F_d_dL = in.d_dL_d_F.inverse();
267  const RankFourTensor d_dW_d_dL = in.d_dW_d_F * d_F_d_dL;
268  const RankFourTensor d_dD_d_dL = RankFourTensor::IdentityFour() - d_dW_d_dL;
269  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  const RankTwoTensor SR = S_inner * rhatT; // S r_hat^T (shape (m, j))
275  const RankTwoTensor RS = rhat * S_inner; // r_hat S (shape (i, m))
276  const RankFourTensor T1 = SR.times<m_, j_, i_, m_, k_, l_>(d_rhat_d_dL);
277  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  const RankFourTensor T3 = J_inv * (in.small_jacobian * d_dD_d_dL);
281 
282  out.cauchy_jacobian = T1 + T2 + T3;
283  return out;
284 }
285 
286 // ============================================================================
287 // Dispatch
288 // ============================================================================
289 Outputs
290 compute(const MooseEnum & rate, const Inputs & in, bool need_jacobian)
291 {
292  const std::string s = rate;
293  if (s == "truesdell")
294  return truesdell(in, need_jacobian);
295  if (s == "jaumann")
296  return jaumann(in, need_jacobian);
297  if (s == "green_naghdi")
298  return greenNaghdi(in, need_jacobian);
299  if (s == "rashid")
300  return rashid(in, need_jacobian);
301  mooseError("Unknown objective_rate value: ", s);
302 }
303 }
RankFourTensorTempl< T > inverse() const
const double T
static RankFourTensorTempl< Real > IdentityFour()
void mooseError(Args &&... args)
RankFourTensor d_dL_d_F
d(dL)/dF and d(dW)/dF from the strain calculator.
Per-qp quantities a rate reads.
Objective-stress-rate updates as stateless free functions.
Outputs greenNaghdi(const Inputs &in, bool need_jacobian)
sigma_{n+1} = J(dO)^{-1} (sigma_n + Deltasigma), with dO built from the polar rotation increment...
const double R
Outputs jaumann(const Inputs &in, bool need_jacobian)
sigma_{n+1} = J(dW)^{-1} (sigma_n + Deltasigma). Jaumann rate.
static RankTwoTensorTempl Identity()
RankTwoTensor dS
Constitutive small-stress increment (_small_stress - _small_stress_old).
RankTwoTensor cauchy_stress_old
Cumulative Cauchy stress at step n.
const std::vector< double > x
static const std::string S
Definition: NS.h:167
Real f(Real x)
Test function for Brents method.
Real doubleContraction(const RankTwoTensorTempl< Real > &a) const
RankFourTensor d_rotation_d_F
Green-Naghdi only: dR/dF.
RankTwoTensorTempl< Real > transpose() const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
RankTwoTensor rotation
Green-Naghdi only: polar-decomposition rotation R (n+1 and n), the inverse incremental deformation gr...
RankFourTensorTempl< Real > times(const RankTwoTensorTempl< Real > &b) const
Outputs truesdell(const Inputs &in, bool need_jacobian)
sigma_{n+1} = J(dL)^{-1} (sigma_n + Deltasigma). Truesdell rate.
OStreamProxy out
IntRange< T > make_range(T beg, T end)
RankTwoTensor dL
Spatial velocity gradient increment dL and its vorticity (skew) part dW.
static const std::complex< double > j(0, 1)
Complex number "j" (also known as "i")
Per-qp outputs a rate produces.
RankFourTensor small_jacobian
Small-strain algorithmic tangent.
Outputs rashid(const Inputs &in, bool need_jacobian)
sigma_{n+1} = r_hat (sigma_n + Deltasigma) r_hat^T, r_hat = exp(Deltaw).
static const std::string k
Definition: NS.h:134
Outputs compute(const MooseEnum &rate, const Inputs &in, bool need_jacobian)
Dispatch to the rate selected by the objective_rate enum (truesdell / jaumann / green_naghdi / rashid...