sparkl2d_core/dynamics/models/
elasticity_corotated_linear.rs1use crate::dynamics::models::ActiveTimestepBounds;
2use crate::dynamics::timestep::ElasticitySoundSpeedTimestepBound;
3use crate::math::{Matrix, Real, Vector};
4
5#[cfg(not(feature = "std"))]
6use na::ComplexField;
7
8#[cfg_attr(feature = "cuda", derive(cust_core::DeviceCopy))]
9#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
10#[derive(Copy, Clone, Debug, PartialEq)]
11#[repr(C)]
12pub struct CorotatedLinearElasticity {
13 pub split_stress_on_failure: bool,
14 pub cfl_coeff: Real,
15 pub lambda: Real,
16 pub mu: Real,
17}
18
19impl CorotatedLinearElasticity {
20 pub fn new(young_modulus: Real, poisson_ratio: Real) -> Self {
21 let (lambda, mu) = crate::utils::lame_lambda_mu(young_modulus, poisson_ratio);
22
23 Self {
24 split_stress_on_failure: true,
25 cfl_coeff: 0.9,
26 lambda,
27 mu,
28 }
29 }
30
31 pub fn kirchhoff_stress(
32 &self,
33 phase: Real,
34 hardening: Real,
35 deformation_gradient: &Matrix<Real>,
36 ) -> Matrix<Real> {
37 let j = deformation_gradient.determinant();
38 let mut svd = deformation_gradient.svd_unordered(true, true);
39 svd.singular_values.apply(|e| *e = *e - 1.0);
40
41 if phase == 1.0 {
42 (2.0 * self.mu * hardening)
43 * svd.recompose().unwrap()
44 * deformation_gradient.transpose()
45 + (self.lambda * hardening * (j - 1.0) * j) * Matrix::identity()
46 } else {
47 let mut pos_def = svd;
48 pos_def.singular_values.apply(|e| *e = e.max(0.0));
49 let mut neg_def = svd;
50 neg_def.singular_values.apply(|e| *e = e.min(0.0));
51
52 let pos_dev_stress = 2.0
53 * self.mu
54 * hardening
55 * pos_def.recompose().unwrap()
56 * deformation_gradient.transpose();
57 let neg_dev_stress = 2.0
58 * self.mu
59 * hardening
60 * neg_def.recompose().unwrap()
61 * deformation_gradient.transpose();
62
63 let spherical_stress = (self.lambda * hardening * (j - 1.0) * j) * Matrix::identity();
64
65 let (pos_part, neg_part) = if j < 1.0 {
66 (pos_dev_stress, neg_dev_stress + spherical_stress)
67 } else {
68 (pos_dev_stress + spherical_stress, neg_dev_stress)
69 };
70
71 let phase_coeff = if self.split_stress_on_failure && phase == 0.0 {
72 0.0
73 } else {
74 1.0
75 };
76 pos_part * phase_coeff + neg_part
77 }
78 }
79
80 pub fn elastic_energy_density(
83 &self,
84 deformation_gradient: Matrix<Real>,
85 elastic_hardening: Real,
86 ) -> Real {
87 let singular_values = deformation_gradient
88 .svd_unordered(false, false)
89 .singular_values;
90 let determinant: Real = singular_values.iter().product();
91
92 let hardened_mu = self.mu * elastic_hardening;
93 let hardened_lambda = self.lambda * elastic_hardening;
94
95 hardened_mu
96 * singular_values
97 .iter()
98 .map(|sigma| (sigma - 1.).powi(2))
99 .sum::<Real>()
100 + hardened_lambda / 2. * (determinant - 1.).powi(2)
101 }
102
103 pub fn pos_energy(&self, deformation_gradient: Matrix<Real>, elastic_hardening: Real) -> Real {
106 let j = deformation_gradient.determinant();
107 let mut pos_def = deformation_gradient.svd_unordered(false, false);
108 pos_def.singular_values.apply(|e| *e = (*e - 1.0).max(0.0));
109
110 let pos_dev_part = self.mu * elastic_hardening * pos_def.singular_values.norm_squared();
111 let spherical_part = self.lambda * elastic_hardening / 2.0 * (j - 1.0) * (j - 1.0);
112
113 if j < 1.0 {
114 pos_dev_part
115 } else {
116 pos_dev_part + spherical_part
117 }
118 }
119
120 pub fn active_timestep_bounds(&self) -> ActiveTimestepBounds {
121 ActiveTimestepBounds::CONSTITUTIVE_MODEL_BOUND
122 | ActiveTimestepBounds::PARTICLE_VELOCITY_BOUND
123 | ActiveTimestepBounds::DEFORMATION_GRADIENT_CHANGE_BOUND
124 }
125
126 pub fn timestep_bound(
127 &self,
128 particle_density0: Real,
129 particle_velocity: &Vector<Real>,
130 elastic_hardening: Real,
131 cell_width: Real,
132 ) -> Real {
133 let bulk_modulus = crate::utils::bulk_modulus_from_lame(self.lambda, self.mu);
134 let shear_modulus = crate::utils::shear_modulus_from_lame(self.lambda, self.mu);
135
136 let bound = ElasticitySoundSpeedTimestepBound {
137 alpha: self.cfl_coeff,
138 bulk_modulus: bulk_modulus * elastic_hardening,
139 shear_modulus: shear_modulus * elastic_hardening,
140 };
141 bound.timestep_bound(particle_density0, particle_velocity, cell_width)
142 }
143
144 pub fn is_fluid(&self) -> bool {
145 false
146 }
147}