conspire/constitutive/solid/viscoelastic/
mod.rs

1//! Viscoelastic constitutive models.
2//!
3//! ---
4//!
5//! Viscoelastic constitutive models cannot be defined by a Helmholtz free energy density function and 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::Explicit,
51    optimize::{
52        EqualityConstraint, FirstOrderRootFinding, OptimizationError, ZerothOrderRootFinding,
53    },
54};
55
56/// Possible applied loads.
57pub enum AppliedLoad<'a> {
58    /// Uniaxial stress given $`\dot{F}_{11}`$.
59    UniaxialStress(fn(Scalar) -> Scalar, &'a [Scalar]),
60    /// Biaxial stress given $`\dot{F}_{11}`$ and $`\dot{F}_{22}`$.
61    BiaxialStress(fn(Scalar) -> Scalar, fn(Scalar) -> Scalar, &'a [Scalar]),
62}
63
64/// Required methods for viscoelastic constitutive models.
65pub trait Viscoelastic
66where
67    Self: Solid + Viscous,
68{
69    /// Calculates and returns the Cauchy stress.
70    ///
71    /// ```math
72    /// \boldsymbol{\sigma} = J^{-1}\mathbf{P}\cdot\mathbf{F}^T
73    /// ```
74    fn cauchy_stress(
75        &self,
76        deformation_gradient: &DeformationGradient,
77        deformation_gradient_rate: &DeformationGradientRate,
78    ) -> Result<CauchyStress, ConstitutiveError> {
79        Ok(deformation_gradient
80            * self
81                .second_piola_kirchhoff_stress(deformation_gradient, deformation_gradient_rate)?
82            * deformation_gradient.transpose()
83            / deformation_gradient.determinant())
84    }
85    /// Calculates and returns the rate tangent stiffness associated with the Cauchy stress.
86    ///
87    /// ```math
88    /// \mathcal{V}_{ijkL} = \frac{\partial\sigma_{ij}}{\partial\dot{F}_{kL}} = J^{-1} \mathcal{W}_{MNkL} F_{iM} F_{jN}
89    /// ```
90    fn cauchy_rate_tangent_stiffness(
91        &self,
92        deformation_gradient: &DeformationGradient,
93        deformation_gradient_rate: &DeformationGradientRate,
94    ) -> Result<CauchyRateTangentStiffness, ConstitutiveError> {
95        Ok(self
96            .second_piola_kirchhoff_rate_tangent_stiffness(
97                deformation_gradient,
98                deformation_gradient_rate,
99            )?
100            .contract_first_second_indices_with_second_indices_of(
101                deformation_gradient,
102                deformation_gradient,
103            )
104            / deformation_gradient.determinant())
105    }
106    /// Calculates and returns the first Piola-Kirchhoff stress.
107    ///
108    /// ```math
109    /// \mathbf{P} = J\boldsymbol{\sigma}\cdot\mathbf{F}^{-T}
110    /// ```
111    fn first_piola_kirchhoff_stress(
112        &self,
113        deformation_gradient: &DeformationGradient,
114        deformation_gradient_rate: &DeformationGradientRate,
115    ) -> Result<FirstPiolaKirchhoffStress, ConstitutiveError> {
116        Ok(
117            self.cauchy_stress(deformation_gradient, deformation_gradient_rate)?
118                * deformation_gradient.inverse_transpose()
119                * deformation_gradient.determinant(),
120        )
121    }
122    /// Calculates and returns the rate tangent stiffness associated with the first Piola-Kirchhoff stress.
123    ///
124    /// ```math
125    /// \mathcal{U}_{iJkL} = \frac{\partial P_{iJ}}{\partial\dot{F}_{kL}} = J \mathcal{V}_{iskL} F_{sJ}^{-T}
126    /// ```
127    fn first_piola_kirchhoff_rate_tangent_stiffness(
128        &self,
129        deformation_gradient: &DeformationGradient,
130        deformation_gradient_rate: &DeformationGradientRate,
131    ) -> Result<FirstPiolaKirchhoffRateTangentStiffness, ConstitutiveError> {
132        Ok(self
133            .cauchy_rate_tangent_stiffness(deformation_gradient, deformation_gradient_rate)?
134            .contract_second_index_with_first_index_of(&deformation_gradient.inverse_transpose())
135            * deformation_gradient.determinant())
136    }
137    /// Calculates and returns the second Piola-Kirchhoff stress.
138    ///
139    /// ```math
140    /// \mathbf{S} = \mathbf{F}^{-1}\cdot\mathbf{P}
141    /// ```
142    fn second_piola_kirchhoff_stress(
143        &self,
144        deformation_gradient: &DeformationGradient,
145        deformation_gradient_rate: &DeformationGradientRate,
146    ) -> Result<SecondPiolaKirchhoffStress, ConstitutiveError> {
147        Ok(deformation_gradient.inverse()
148            * self.cauchy_stress(deformation_gradient, deformation_gradient_rate)?
149            * deformation_gradient.inverse_transpose()
150            * deformation_gradient.determinant())
151    }
152    /// Calculates and returns the rate tangent stiffness associated with the second Piola-Kirchhoff stress.
153    ///
154    /// ```math
155    /// \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}
156    /// ```
157    fn second_piola_kirchhoff_rate_tangent_stiffness(
158        &self,
159        deformation_gradient: &DeformationGradient,
160        deformation_gradient_rate: &DeformationGradientRate,
161    ) -> Result<SecondPiolaKirchhoffRateTangentStiffness, ConstitutiveError> {
162        let deformation_gradient_inverse = deformation_gradient.inverse();
163        Ok(self
164            .cauchy_rate_tangent_stiffness(deformation_gradient, deformation_gradient_rate)?
165            .contract_first_second_indices_with_second_indices_of(
166                &deformation_gradient_inverse,
167                &deformation_gradient_inverse,
168            )
169            * deformation_gradient.determinant())
170    }
171}
172
173/// Zeroth-order root-finding methods for viscoelastic constitutive models.
174pub trait ZerothOrderRoot {
175    /// Solve for the unknown components of the deformation gradient and rate under an applied load.
176    ///
177    /// ```math
178    /// \mathbf{P}(\mathbf{F},\dot{\mathbf{F}}) - \boldsymbol{\lambda} - \mathbf{P}_0 = \mathbf{0}
179    /// ```
180    fn root(
181        &self,
182        applied_load: AppliedLoad,
183        integrator: impl Explicit<DeformationGradientRate, DeformationGradientRates>,
184        solver: impl ZerothOrderRootFinding<DeformationGradient>,
185    ) -> Result<(Times, DeformationGradients, DeformationGradientRates), ConstitutiveError>;
186    #[doc(hidden)]
187    fn root_inner_0(
188        &self,
189        deformation_gradient: &DeformationGradient,
190        equality_constraint: EqualityConstraint,
191        solver: &impl ZerothOrderRootFinding<DeformationGradient>,
192        initial_guess: &DeformationGradientRate,
193    ) -> Result<DeformationGradientRate, OptimizationError>;
194}
195
196/// Zeroth-order root-finding methods for viscoelastic constitutive models.
197pub trait FirstOrderRoot {
198    /// Solve for the unknown components of the deformation gradient and rate under an applied load.
199    ///
200    /// ```math
201    /// \mathbf{P}(\mathbf{F},\dot{\mathbf{F}}) - \boldsymbol{\lambda} - \mathbf{P}_0 = \mathbf{0}
202    /// ```
203    fn root(
204        &self,
205        applied_load: AppliedLoad,
206        integrator: impl Explicit<DeformationGradientRate, DeformationGradientRates>,
207        solver: impl FirstOrderRootFinding<
208            FirstPiolaKirchhoffStress,
209            FirstPiolaKirchhoffRateTangentStiffness,
210            DeformationGradientRate,
211        >,
212    ) -> Result<(Times, DeformationGradients, DeformationGradientRates), ConstitutiveError>;
213    #[doc(hidden)]
214    fn root_inner_1(
215        &self,
216        deformation_gradient: &DeformationGradient,
217        equality_constraint: EqualityConstraint,
218        solver: &impl FirstOrderRootFinding<
219            FirstPiolaKirchhoffStress,
220            FirstPiolaKirchhoffRateTangentStiffness,
221            DeformationGradientRate,
222        >,
223        initial_guess: &DeformationGradientRate,
224    ) -> Result<DeformationGradientRate, OptimizationError>;
225}
226
227impl<T> ZerothOrderRoot for T
228where
229    T: Viscoelastic,
230{
231    fn root(
232        &self,
233        applied_load: AppliedLoad,
234        integrator: impl Explicit<DeformationGradientRate, DeformationGradientRates>,
235        solver: impl ZerothOrderRootFinding<DeformationGradientRate>,
236    ) -> Result<(Times, DeformationGradients, DeformationGradientRates), ConstitutiveError> {
237        let mut solution = DeformationGradientRate::zero();
238        match match applied_load {
239            AppliedLoad::UniaxialStress(deformation_gradient_rate_11, time) => {
240                let mut matrix = Matrix::zero(4, 9);
241                let mut vector = Vector::zero(4);
242                matrix[0][0] = 1.0;
243                matrix[1][1] = 1.0;
244                matrix[2][2] = 1.0;
245                matrix[3][5] = 1.0;
246                integrator.integrate(
247                    |t: Scalar, deformation_gradient: &DeformationGradient| {
248                        vector[0] = deformation_gradient_rate_11(t);
249                        solution = self.root_inner_0(
250                            deformation_gradient,
251                            EqualityConstraint::Linear(matrix.clone(), vector.clone()),
252                            &solver,
253                            &solution,
254                        )?;
255                        Ok(solution.clone())
256                    },
257                    time,
258                    DeformationGradient::identity(),
259                )
260            }
261            AppliedLoad::BiaxialStress(
262                deformation_gradient_rate_11,
263                deformation_gradient_rate_22,
264                time,
265            ) => {
266                let mut matrix = Matrix::zero(5, 9);
267                let mut vector = Vector::zero(5);
268                matrix[0][0] = 1.0;
269                matrix[1][1] = 1.0;
270                matrix[2][2] = 1.0;
271                matrix[3][5] = 1.0;
272                matrix[4][4] = 1.0;
273                integrator.integrate(
274                    |t: Scalar, deformation_gradient: &DeformationGradient| {
275                        vector[0] = deformation_gradient_rate_11(t);
276                        vector[4] = deformation_gradient_rate_22(t);
277                        solution = self.root_inner_0(
278                            deformation_gradient,
279                            EqualityConstraint::Linear(matrix.clone(), vector.clone()),
280                            &solver,
281                            &solution,
282                        )?;
283                        Ok(solution.clone())
284                    },
285                    time,
286                    DeformationGradient::identity(),
287                )
288            }
289        } {
290            Ok(results) => Ok(results),
291            Err(error) => Err(ConstitutiveError::Upstream(
292                format!("{error}"),
293                format!("{self:?}"),
294            )),
295        }
296    }
297    fn root_inner_0(
298        &self,
299        deformation_gradient: &DeformationGradient,
300        equality_constraint: EqualityConstraint,
301        solver: &impl ZerothOrderRootFinding<DeformationGradientRate>,
302        initial_guess: &DeformationGradientRate,
303    ) -> Result<DeformationGradientRate, OptimizationError> {
304        solver.root(
305            |deformation_gradient_rate: &DeformationGradientRate| {
306                Ok(self.first_piola_kirchhoff_stress(
307                    deformation_gradient,
308                    deformation_gradient_rate,
309                )?)
310            },
311            initial_guess.clone(),
312            equality_constraint,
313        )
314    }
315}
316
317impl<T> FirstOrderRoot for T
318where
319    T: Viscoelastic,
320{
321    fn root(
322        &self,
323        applied_load: AppliedLoad,
324        integrator: impl Explicit<DeformationGradientRate, DeformationGradientRates>,
325        solver: impl FirstOrderRootFinding<
326            FirstPiolaKirchhoffStress,
327            FirstPiolaKirchhoffRateTangentStiffness,
328            DeformationGradientRate,
329        >,
330    ) -> Result<(Times, DeformationGradients, DeformationGradientRates), ConstitutiveError> {
331        let mut solution = DeformationGradientRate::zero();
332        match match applied_load {
333            AppliedLoad::UniaxialStress(deformation_gradient_rate_11, time) => {
334                let mut matrix = Matrix::zero(4, 9);
335                let mut vector = Vector::zero(4);
336                matrix[0][0] = 1.0;
337                matrix[1][1] = 1.0;
338                matrix[2][2] = 1.0;
339                matrix[3][5] = 1.0;
340                integrator.integrate(
341                    |t: Scalar, deformation_gradient: &DeformationGradient| {
342                        vector[0] = deformation_gradient_rate_11(t);
343                        solution = self.root_inner_1(
344                            deformation_gradient,
345                            EqualityConstraint::Linear(matrix.clone(), vector.clone()),
346                            &solver,
347                            &solution,
348                        )?;
349                        Ok(solution.clone())
350                    },
351                    time,
352                    DeformationGradient::identity(),
353                )
354            }
355            AppliedLoad::BiaxialStress(
356                deformation_gradient_rate_11,
357                deformation_gradient_rate_22,
358                time,
359            ) => {
360                let mut matrix = Matrix::zero(5, 9);
361                let mut vector = Vector::zero(5);
362                matrix[0][0] = 1.0;
363                matrix[1][1] = 1.0;
364                matrix[2][2] = 1.0;
365                matrix[3][5] = 1.0;
366                matrix[4][4] = 1.0;
367                integrator.integrate(
368                    |t: Scalar, deformation_gradient: &DeformationGradient| {
369                        vector[0] = deformation_gradient_rate_11(t);
370                        vector[4] = deformation_gradient_rate_22(t);
371                        solution = self.root_inner_1(
372                            deformation_gradient,
373                            EqualityConstraint::Linear(matrix.clone(), vector.clone()),
374                            &solver,
375                            &solution,
376                        )?;
377                        Ok(solution.clone())
378                    },
379                    time,
380                    DeformationGradient::identity(),
381                )
382            }
383        } {
384            Ok(results) => Ok(results),
385            Err(error) => Err(ConstitutiveError::Upstream(
386                format!("{error}"),
387                format!("{self:?}"),
388            )),
389        }
390    }
391    fn root_inner_1(
392        &self,
393        deformation_gradient: &DeformationGradient,
394        equality_constraint: EqualityConstraint,
395        solver: &impl FirstOrderRootFinding<
396            FirstPiolaKirchhoffStress,
397            FirstPiolaKirchhoffRateTangentStiffness,
398            DeformationGradientRate,
399        >,
400        initial_guess: &DeformationGradientRate,
401    ) -> Result<DeformationGradientRate, OptimizationError> {
402        solver.root(
403            |deformation_gradient_rate: &DeformationGradientRate| {
404                Ok(self.first_piola_kirchhoff_stress(
405                    deformation_gradient,
406                    deformation_gradient_rate,
407                )?)
408            },
409            |deformation_gradient_rate: &DeformationGradientRate| {
410                Ok(self.first_piola_kirchhoff_rate_tangent_stiffness(
411                    deformation_gradient,
412                    deformation_gradient_rate,
413                )?)
414            },
415            initial_guess.clone(),
416            equality_constraint,
417        )
418    }
419}