conspire/constitutive/solid/viscoelastic/
mod.rs

1//! Viscoelastic solid constitutive models.
2//!
3//! ---
4//!
5//! Viscoelastic solid constitutive models cannot be defined by a Helmholtz free energy density and a viscous dissipation function.
6//! These constitutive models are therefore defined by a relation for the stress as a function of the deformation gradient and rate.
7//! Consequently, the rate tangent stiffness associated with the first Piola-Kirchhoff stress is not symmetric for these models.
8//!
9//! ```math
10//! \mathcal{U}_{iJkL} \neq \mathcal{U}_{kLiJ}
11//! ```
12
13// Could eventually make this a bit more general with a const generic type parameters:
14// (0) implicitly rate dependent (has rate-dependent ISV)
15// (1) explicitly rate dependent to first order (dF/dt)
16// (2) explicitly rate dependent to second order (d^2F/dt^2)
17// ...
18// Still need separate traits, though, since the arguments will differ.
19// And will have to implement differently anyway, especially in fem/
20// So maybe instead, there are just subdirectories in viscoelastic/
21// order_0/
22// order_1/
23// order_2/
24// ...
25// But how to handle order_1+ that may or may not have ISV?
26// Maybe just have separate trait, like ElastiicViscoplasticISV.
27// But what about viscoelastic models with rate-dependent and/or rate-independent ISV?
28// Maybe the key is there are *IVs* and *SVs*:
29// internal variables are determined by the state (not independent), and cannot be rate-dependent,
30// and state variables determine the state (are independent), and must be rate-dependent.
31// The ability (or inability) to set these models up properly as
32// (a) optimization (or root-finding) problems with constraints, and
33// (b) ensure that the second law is satisfied (Lyapunov stability),
34// should determine whether the model can fit into this framework.
35// For example, if a model has rate-independent variables but is path-dependent,
36// the model cannot fit into this framework, and may be invalid altogether,
37// or at least that doing the model that was is a bad idea.
38// That might be the key question:
39// Can (a) and (b) be satisfied for rate-independent, but path-dependent, variables?
40// Rate-independent elasto-plasticity being the key example.
41// If (a) is true, the plastic deformation is not independent.
42// But if (a) is not true, what problem are we solving?
43
44#[cfg(test)]
45pub mod test;
46
47use super::{super::fluid::viscous::Viscous, *};
48use crate::math::{
49    Matrix, Vector,
50    integrate::{ImplicitDaeFirstOrderRoot, ImplicitDaeZerothOrderRoot},
51    optimize::{EqualityConstraint, FirstOrderRootFinding, ZerothOrderRootFinding},
52};
53
54/// Possible applied loads.
55pub enum AppliedLoad<'a> {
56    /// Uniaxial stress given $`\dot{F}_{11}`$.
57    UniaxialStress(fn(Scalar) -> Scalar, &'a [Scalar]),
58    /// Biaxial stress given $`\dot{F}_{11}`$ and $`\dot{F}_{22}`$.
59    BiaxialStress(fn(Scalar) -> Scalar, fn(Scalar) -> Scalar, &'a [Scalar]),
60}
61
62/// Required methods for viscoelastic solid constitutive models.
63pub trait Viscoelastic
64where
65    Self: Solid + Viscous,
66{
67    /// Calculates and returns the Cauchy stress.
68    ///
69    /// ```math
70    /// \boldsymbol{\sigma} = J^{-1}\mathbf{P}\cdot\mathbf{F}^T
71    /// ```
72    fn cauchy_stress(
73        &self,
74        deformation_gradient: &DeformationGradient,
75        deformation_gradient_rate: &DeformationGradientRate,
76    ) -> Result<CauchyStress, ConstitutiveError> {
77        Ok(deformation_gradient
78            * self
79                .second_piola_kirchhoff_stress(deformation_gradient, deformation_gradient_rate)?
80            * deformation_gradient.transpose()
81            / deformation_gradient.determinant())
82    }
83    /// Calculates and returns the rate tangent stiffness associated with the Cauchy stress.
84    ///
85    /// ```math
86    /// \mathcal{V}_{ijkL} = \frac{\partial\sigma_{ij}}{\partial\dot{F}_{kL}} = J^{-1} \mathcal{W}_{MNkL} F_{iM} F_{jN}
87    /// ```
88    fn cauchy_rate_tangent_stiffness(
89        &self,
90        deformation_gradient: &DeformationGradient,
91        deformation_gradient_rate: &DeformationGradientRate,
92    ) -> Result<CauchyRateTangentStiffness, ConstitutiveError> {
93        Ok(self
94            .second_piola_kirchhoff_rate_tangent_stiffness(
95                deformation_gradient,
96                deformation_gradient_rate,
97            )?
98            .contract_first_second_indices_with_second_indices_of(
99                deformation_gradient,
100                deformation_gradient,
101            )
102            / deformation_gradient.determinant())
103    }
104    /// Calculates and returns the first Piola-Kirchhoff stress.
105    ///
106    /// ```math
107    /// \mathbf{P} = J\boldsymbol{\sigma}\cdot\mathbf{F}^{-T}
108    /// ```
109    fn first_piola_kirchhoff_stress(
110        &self,
111        deformation_gradient: &DeformationGradient,
112        deformation_gradient_rate: &DeformationGradientRate,
113    ) -> Result<FirstPiolaKirchhoffStress, ConstitutiveError> {
114        Ok(
115            self.cauchy_stress(deformation_gradient, deformation_gradient_rate)?
116                * deformation_gradient.inverse_transpose()
117                * deformation_gradient.determinant(),
118        )
119    }
120    /// Calculates and returns the rate tangent stiffness associated with the first Piola-Kirchhoff stress.
121    ///
122    /// ```math
123    /// \mathcal{U}_{iJkL} = \frac{\partial P_{iJ}}{\partial\dot{F}_{kL}} = J \mathcal{V}_{iskL} F_{sJ}^{-T}
124    /// ```
125    fn first_piola_kirchhoff_rate_tangent_stiffness(
126        &self,
127        deformation_gradient: &DeformationGradient,
128        deformation_gradient_rate: &DeformationGradientRate,
129    ) -> Result<FirstPiolaKirchhoffRateTangentStiffness, ConstitutiveError> {
130        Ok(self
131            .cauchy_rate_tangent_stiffness(deformation_gradient, deformation_gradient_rate)?
132            .contract_second_index_with_first_index_of(&deformation_gradient.inverse_transpose())
133            * deformation_gradient.determinant())
134    }
135    /// Calculates and returns the second Piola-Kirchhoff stress.
136    ///
137    /// ```math
138    /// \mathbf{S} = \mathbf{F}^{-1}\cdot\mathbf{P}
139    /// ```
140    fn second_piola_kirchhoff_stress(
141        &self,
142        deformation_gradient: &DeformationGradient,
143        deformation_gradient_rate: &DeformationGradientRate,
144    ) -> Result<SecondPiolaKirchhoffStress, ConstitutiveError> {
145        Ok(deformation_gradient.inverse()
146            * self.cauchy_stress(deformation_gradient, deformation_gradient_rate)?
147            * deformation_gradient.inverse_transpose()
148            * deformation_gradient.determinant())
149    }
150    /// Calculates and returns the rate tangent stiffness associated with the second Piola-Kirchhoff stress.
151    ///
152    /// ```math
153    /// \mathcal{W}_{IJkL} = \frac{\partial S_{IJ}}{\partial\dot{F}_{kL}} = \mathcal{U}_{mJkL}F_{mI}^{-T} = J \mathcal{V}_{mnkL} F_{mI}^{-T} F_{nJ}^{-T}
154    /// ```
155    fn second_piola_kirchhoff_rate_tangent_stiffness(
156        &self,
157        deformation_gradient: &DeformationGradient,
158        deformation_gradient_rate: &DeformationGradientRate,
159    ) -> Result<SecondPiolaKirchhoffRateTangentStiffness, ConstitutiveError> {
160        let deformation_gradient_inverse = deformation_gradient.inverse();
161        Ok(self
162            .cauchy_rate_tangent_stiffness(deformation_gradient, deformation_gradient_rate)?
163            .contract_first_second_indices_with_second_indices_of(
164                &deformation_gradient_inverse,
165                &deformation_gradient_inverse,
166            )
167            * deformation_gradient.determinant())
168    }
169}
170
171/// Zeroth-order root-finding methods for viscoelastic solid constitutive models.
172pub trait ZerothOrderRoot {
173    /// Solve for the unknown components of the deformation gradient and rate under an applied load.
174    ///
175    /// ```math
176    /// \mathbf{P}(\mathbf{F},\dot{\mathbf{F}}) - \boldsymbol{\lambda} - \mathbf{P}_0 = \mathbf{0}
177    /// ```
178    fn root(
179        &self,
180        applied_load: AppliedLoad,
181        integrator: impl ImplicitDaeZerothOrderRoot<DeformationGradient, DeformationGradients>,
182        solver: impl ZerothOrderRootFinding<DeformationGradient>,
183    ) -> Result<(Times, DeformationGradients, DeformationGradientRates), ConstitutiveError>;
184}
185
186/// Zeroth-order root-finding methods for viscoelastic solid constitutive models.
187pub trait FirstOrderRoot {
188    /// Solve for the unknown components of the deformation gradient and rate under an applied load.
189    ///
190    /// ```math
191    /// \mathbf{P}(\mathbf{F},\dot{\mathbf{F}}) - \boldsymbol{\lambda} - \mathbf{P}_0 = \mathbf{0}
192    /// ```
193    fn root(
194        &self,
195        applied_load: AppliedLoad,
196        integrator: impl ImplicitDaeFirstOrderRoot<
197            FirstPiolaKirchhoffStress,
198            FirstPiolaKirchhoffRateTangentStiffness,
199            DeformationGradientRate,
200            DeformationGradientRates,
201        >,
202        solver: impl FirstOrderRootFinding<
203            FirstPiolaKirchhoffStress,
204            FirstPiolaKirchhoffRateTangentStiffness,
205            DeformationGradientRate,
206        >,
207    ) -> Result<(Times, DeformationGradients, DeformationGradientRates), ConstitutiveError>;
208}
209
210impl<T> ZerothOrderRoot for T
211where
212    T: Viscoelastic,
213{
214    fn root(
215        &self,
216        applied_load: AppliedLoad,
217        integrator: impl ImplicitDaeZerothOrderRoot<DeformationGradient, DeformationGradients>,
218        solver: impl ZerothOrderRootFinding<DeformationGradientRate>,
219    ) -> Result<(Times, DeformationGradients, DeformationGradientRates), ConstitutiveError> {
220        match match applied_load {
221            AppliedLoad::UniaxialStress(deformation_gradient_rate_11, time) => {
222                let mut matrix = Matrix::zero(4, 9);
223                let mut vector = Vector::zero(4);
224                matrix[0][0] = 1.0;
225                matrix[1][1] = 1.0;
226                matrix[2][2] = 1.0;
227                matrix[3][5] = 1.0;
228                integrator.integrate(
229                    |_: Scalar,
230                     deformation_gradient: &DeformationGradient,
231                     deformation_gradient_rate: &DeformationGradientRate| {
232                        Ok(self.first_piola_kirchhoff_stress(
233                            deformation_gradient,
234                            deformation_gradient_rate,
235                        )?)
236                    },
237                    solver,
238                    time,
239                    DeformationGradient::identity(),
240                    |t: Scalar| {
241                        vector[0] = deformation_gradient_rate_11(t);
242                        EqualityConstraint::Linear(matrix.clone(), vector.clone())
243                    },
244                )
245            }
246            AppliedLoad::BiaxialStress(
247                deformation_gradient_rate_11,
248                deformation_gradient_rate_22,
249                time,
250            ) => {
251                let mut matrix = Matrix::zero(5, 9);
252                let mut vector = Vector::zero(5);
253                matrix[0][0] = 1.0;
254                matrix[1][1] = 1.0;
255                matrix[2][2] = 1.0;
256                matrix[3][5] = 1.0;
257                matrix[4][4] = 1.0;
258                integrator.integrate(
259                    |_: Scalar,
260                     deformation_gradient: &DeformationGradient,
261                     deformation_gradient_rate: &DeformationGradientRate| {
262                        Ok(self.first_piola_kirchhoff_stress(
263                            deformation_gradient,
264                            deformation_gradient_rate,
265                        )?)
266                    },
267                    solver,
268                    time,
269                    DeformationGradient::identity(),
270                    |t: Scalar| {
271                        vector[0] = deformation_gradient_rate_11(t);
272                        vector[4] = deformation_gradient_rate_22(t);
273                        EqualityConstraint::Linear(matrix.clone(), vector.clone())
274                    },
275                )
276            }
277        } {
278            Ok(results) => Ok(results),
279            Err(error) => Err(ConstitutiveError::Upstream(
280                format!("{error}"),
281                format!("{self:?}"),
282            )),
283        }
284    }
285}
286
287impl<T> FirstOrderRoot for T
288where
289    T: Viscoelastic,
290{
291    fn root(
292        &self,
293        applied_load: AppliedLoad,
294        integrator: impl ImplicitDaeFirstOrderRoot<
295            FirstPiolaKirchhoffStress,
296            FirstPiolaKirchhoffRateTangentStiffness,
297            DeformationGradientRate,
298            DeformationGradientRates,
299        >,
300        solver: impl FirstOrderRootFinding<
301            FirstPiolaKirchhoffStress,
302            FirstPiolaKirchhoffRateTangentStiffness,
303            DeformationGradientRate,
304        >,
305    ) -> Result<(Times, DeformationGradients, DeformationGradientRates), ConstitutiveError> {
306        match match applied_load {
307            AppliedLoad::UniaxialStress(deformation_gradient_rate_11, time) => {
308                let mut matrix = Matrix::zero(4, 9);
309                let mut vector = Vector::zero(4);
310                matrix[0][0] = 1.0;
311                matrix[1][1] = 1.0;
312                matrix[2][2] = 1.0;
313                matrix[3][5] = 1.0;
314                integrator.integrate(
315                    |_: Scalar,
316                     deformation_gradient: &DeformationGradient,
317                     deformation_gradient_rate: &DeformationGradientRate| {
318                        Ok(self.first_piola_kirchhoff_stress(
319                            deformation_gradient,
320                            deformation_gradient_rate,
321                        )?)
322                    },
323                    |_: Scalar,
324                     deformation_gradient: &DeformationGradient,
325                     deformation_gradient_rate: &DeformationGradientRate| {
326                        Ok(self.first_piola_kirchhoff_rate_tangent_stiffness(
327                            deformation_gradient,
328                            deformation_gradient_rate,
329                        )?)
330                    },
331                    solver,
332                    time,
333                    DeformationGradient::identity(),
334                    |t: Scalar| {
335                        vector[0] = deformation_gradient_rate_11(t);
336                        EqualityConstraint::Linear(matrix.clone(), vector.clone())
337                    },
338                )
339            }
340            AppliedLoad::BiaxialStress(
341                deformation_gradient_rate_11,
342                deformation_gradient_rate_22,
343                time,
344            ) => {
345                let mut matrix = Matrix::zero(5, 9);
346                let mut vector = Vector::zero(5);
347                matrix[0][0] = 1.0;
348                matrix[1][1] = 1.0;
349                matrix[2][2] = 1.0;
350                matrix[3][5] = 1.0;
351                matrix[4][4] = 1.0;
352                integrator.integrate(
353                    |_: Scalar,
354                     deformation_gradient: &DeformationGradient,
355                     deformation_gradient_rate: &DeformationGradientRate| {
356                        Ok(self.first_piola_kirchhoff_stress(
357                            deformation_gradient,
358                            deformation_gradient_rate,
359                        )?)
360                    },
361                    |_: Scalar,
362                     deformation_gradient: &DeformationGradient,
363                     deformation_gradient_rate: &DeformationGradientRate| {
364                        Ok(self.first_piola_kirchhoff_rate_tangent_stiffness(
365                            deformation_gradient,
366                            deformation_gradient_rate,
367                        )?)
368                    },
369                    solver,
370                    time,
371                    DeformationGradient::identity(),
372                    |t: Scalar| {
373                        vector[0] = deformation_gradient_rate_11(t);
374                        vector[4] = deformation_gradient_rate_22(t);
375                        EqualityConstraint::Linear(matrix.clone(), vector.clone())
376                    },
377                )
378            }
379        } {
380            Ok(results) => Ok(results),
381            Err(error) => Err(ConstitutiveError::Upstream(
382                format!("{error}"),
383                format!("{self:?}"),
384            )),
385        }
386    }
387}