1use nalgebra::{Matrix3, Matrix6, Vector6};
4use serde::{Deserialize, Serialize};
5
6use crate::tensor::{StressTensor, StrainTensor};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct HookeIsotropic {
11 pub youngs_modulus: f64,
13 pub poissons_ratio: f64,
15}
16
17impl HookeIsotropic {
18 pub fn new(e: f64, nu: f64) -> Self {
20 Self { youngs_modulus: e, poissons_ratio: nu }
21 }
22
23 pub fn shear_modulus(&self) -> f64 {
25 self.youngs_modulus / (2.0 * (1.0 + self.poissons_ratio))
26 }
27
28 pub fn bulk_modulus(&self) -> f64 {
30 self.youngs_modulus / (3.0 * (1.0 - 2.0 * self.poissons_ratio))
31 }
32
33 pub fn lame_lambda(&self) -> f64 {
35 let nu = self.poissons_ratio;
36 nu * self.youngs_modulus / ((1.0 + nu) * (1.0 - 2.0 * nu))
37 }
38
39 pub fn lame_mu(&self) -> f64 {
41 self.shear_modulus()
42 }
43
44 pub fn stress_from_strain(&self, strain: &StrainTensor) -> StressTensor {
47 let lam = self.lame_lambda();
48 let mu = self.lame_mu();
49 let tr = strain.matrix.trace();
50 let sigma = lam * tr * Matrix3::identity() + 2.0 * mu * strain.matrix;
51 StressTensor { matrix: sigma }
52 }
53
54 pub fn strain_from_stress(&self, stress: &StressTensor) -> StrainTensor {
56 let e = self.youngs_modulus;
57 let nu = self.poissons_ratio;
58 let s = &stress.matrix;
59 let tr = s.trace();
60 let eps = ((1.0 + nu) / e) * *s - (nu / e) * tr * Matrix3::identity();
61 StrainTensor { matrix: eps }
62 }
63
64 pub fn stiffness_matrix_voigt(&self) -> Matrix6<f64> {
66 let nu = self.poissons_ratio;
67 let e = self.youngs_modulus;
68 let factor = e / ((1.0 + nu) * (1.0 - 2.0 * nu));
69 let lam_nu = 1.0 - nu;
70 let c12 = factor * nu;
71 let c11 = factor * lam_nu;
72 let c44 = e / (2.0 * (1.0 + nu));
73 Matrix6::from_row_slice(&[
74 c11, c12, c12, 0.0, 0.0, 0.0,
75 c12, c11, c12, 0.0, 0.0, 0.0,
76 c12, c12, c11, 0.0, 0.0, 0.0,
77 0.0, 0.0, 0.0, c44, 0.0, 0.0,
78 0.0, 0.0, 0.0, 0.0, c44, 0.0,
79 0.0, 0.0, 0.0, 0.0, 0.0, c44,
80 ])
81 }
82
83 pub fn compliance_matrix_voigt(&self) -> Matrix6<f64> {
85 let e = self.youngs_modulus;
86 let nu = self.poissons_ratio;
87 let s11 = 1.0 / e;
88 let s12 = -nu / e;
89 let s44 = 2.0 * (1.0 + nu) / e;
90 Matrix6::from_row_slice(&[
91 s11, s12, s12, 0.0, 0.0, 0.0,
92 s12, s11, s12, 0.0, 0.0, 0.0,
93 s12, s12, s11, 0.0, 0.0, 0.0,
94 0.0, 0.0, 0.0, s44, 0.0, 0.0,
95 0.0, 0.0, 0.0, 0.0, s44, 0.0,
96 0.0, 0.0, 0.0, 0.0, 0.0, s44,
97 ])
98 }
99
100 pub fn axial_stiffness(&self, area: f64) -> f64 {
102 self.youngs_modulus * area
103 }
104
105 pub fn flexural_stiffness(&self, moment_of_inertia: f64) -> f64 {
107 self.youngs_modulus * moment_of_inertia
108 }
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ElasticityTensor {
114 pub c: Matrix6<f64>,
116}
117
118impl ElasticityTensor {
119 pub fn new(c: Matrix6<f64>) -> Self {
121 Self { c }
122 }
123
124 pub fn orthotropic(
126 e1: f64, e2: f64, e3: f64,
127 nu12: f64, nu13: f64, nu23: f64,
128 g12: f64, g13: f64, g23: f64,
129 ) -> Self {
130 let nu21 = nu12 * e2 / e1;
132 let nu31 = nu13 * e3 / e1;
133 let nu32 = nu23 * e3 / e2;
134 let delta = 1.0 - nu12 * nu21 - nu23 * nu32 - nu31 * nu13 - 2.0 * nu21 * nu32 * nu13;
135
136 let _c11 = (1.0 - nu23 * nu32) / (e2 * e3 * delta);
137 let _c22 = (1.0 - nu13 * nu31) / (e1 * e3 * delta);
138 let _c33 = (1.0 - nu12 * nu21) / (e1 * e2 * delta);
139 let _c12 = (nu21 + nu31 * nu23) / (e1 * e3 * delta);
140 let _c13 = (nu31 + nu21 * nu32) / (e1 * e2 * delta);
141 let _c23 = (nu32 + nu12 * nu31) / (e2 * e1 * delta);
142
143 let s = Matrix6::from_row_slice(&[
145 1.0/e1, -nu12/e1, -nu13/e1, 0.0, 0.0, 0.0,
146 -nu21/e2, 1.0/e2, -nu23/e2, 0.0, 0.0, 0.0,
147 -nu31/e3, -nu32/e3, 1.0/e3, 0.0, 0.0, 0.0,
148 0.0, 0.0, 0.0, 1.0/g12, 0.0, 0.0,
149 0.0, 0.0, 0.0, 0.0, 1.0/g13, 0.0,
150 0.0, 0.0, 0.0, 0.0, 0.0, 1.0/g23,
151 ]);
152
153 Self { c: s.try_inverse().unwrap_or(Matrix6::zeros()) }
154 }
155
156 pub fn stress_from_strain(&self, strain_voigt: &Vector6<f64>) -> Vector6<f64> {
158 self.c * strain_voigt
159 }
160
161 pub fn compliance(&self) -> Option<Matrix6<f64>> {
163 self.c.try_inverse()
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use approx::assert_relative_eq;
171
172 #[test]
173 fn test_hooke_shear_modulus() {
174 let h = HookeIsotropic::new(200e9, 0.3);
175 let g = 200e9 / (2.0 * 1.3);
176 assert_relative_eq!(h.shear_modulus(), g);
177 }
178
179 #[test]
180 fn test_hooke_bulk_modulus() {
181 let h = HookeIsotropic::new(200e9, 0.3);
182 let k = 200e9 / (3.0 * 0.4);
183 assert_relative_eq!(h.bulk_modulus(), k);
184 }
185
186 #[test]
187 fn test_hooke_roundtrip() {
188 let h = HookeIsotropic::new(200e9, 0.3);
189 let strain = StrainTensor::from_components(1e-3, 0.5e-3, -0.5e-3, 0.2e-3, 0.0, 0.0);
190 let stress = h.stress_from_strain(&strain);
191 let strain2 = h.strain_from_stress(&stress);
192 assert_relative_eq!(strain.matrix[(0, 0)], strain2.matrix[(0, 0)], epsilon = 1e-6);
193 assert_relative_eq!(strain.matrix[(1, 1)], strain2.matrix[(1, 1)], epsilon = 1e-6);
194 }
195
196 #[test]
197 fn test_stiffness_compliance_inverse() {
198 let h = HookeIsotropic::new(200e9, 0.3);
199 let c = h.stiffness_matrix_voigt();
200 let s = h.compliance_matrix_voigt();
201 let product = c * s;
202 let identity = Matrix6::identity();
203 for i in 0..6 {
204 for j in 0..6 {
205 assert_relative_eq!(product[(i, j)], identity[(i, j)], epsilon = 1e-3);
206 }
207 }
208 }
209
210 #[test]
211 fn test_orthotropic_creation() {
212 let ortho = ElasticityTensor::orthotropic(
213 140e9, 10e9, 10e9, 0.3, 0.3, 0.4, 5e9, 5e9, 3e9, );
217 assert!(ortho.c.norm() > 0.0);
219 }
220}