1use nalgebra::{Matrix3, Vector3};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct StressTensor {
12 pub matrix: Matrix3<f64>,
14}
15
16impl StressTensor {
17 pub fn new(matrix: Matrix3<f64>) -> Self {
20 let sym = (matrix + matrix.transpose()) * 0.5;
21 Self { matrix: sym }
22 }
23
24 pub fn from_components(
27 sxx: f64, syy: f64, szz: f64,
28 txy: f64, txz: f64, tyz: f64,
29 ) -> Self {
30 Self {
31 matrix: Matrix3::new(
32 sxx, txy, txz,
33 txy, syy, tyz,
34 txz, tyz, szz,
35 ),
36 }
37 }
38
39 pub fn hydrostatic(&self) -> f64 {
41 self.matrix.trace() / 3.0
42 }
43
44 pub fn deviatoric(&self) -> StressTensor {
46 let p = self.hydrostatic();
47 let dev = self.matrix - p * Matrix3::identity();
48 StressTensor { matrix: dev }
49 }
50
51 pub fn von_mises(&self) -> f64 {
53 let s = self.deviatoric().matrix;
54 ((3.0 / 2.0) * (
55 s[(0, 0)] * s[(0, 0)] + s[(1, 1)] * s[(1, 1)] + s[(2, 2)] * s[(2, 2)]
56 + 2.0 * (s[(0, 1)] * s[(0, 1)] + s[(0, 2)] * s[(0, 2)] + s[(1, 2)] * s[(1, 2)])
57 )).sqrt()
58 }
59
60 pub fn first_invariant(&self) -> f64 {
62 self.matrix.trace()
63 }
64
65 pub fn second_invariant(&self) -> f64 {
67 let m = &self.matrix;
68 m[(0, 0)] * m[(1, 1)] + m[(1, 1)] * m[(2, 2)] + m[(2, 2)] * m[(0, 0)]
69 - m[(0, 1)] * m[(0, 1)] - m[(1, 2)] * m[(1, 2)] - m[(0, 2)] * m[(0, 2)]
70 }
71
72 pub fn third_invariant(&self) -> f64 {
74 self.matrix.determinant()
75 }
76
77 pub fn principal_stresses(&self) -> Vector3<f64> {
79 let eig = self.matrix.symmetric_eigen();
80 let mut vals = eig.eigenvalues.data.0[0];
81 vals.sort_by(|a, b| b.partial_cmp(a).unwrap());
82 Vector3::new(vals[0], vals[1], vals[2])
83 }
84
85 pub fn max_shear(&self) -> f64 {
87 let p = self.principal_stresses();
88 (p[0] - p[2]) / 2.0
89 }
90
91 pub fn traction(&self, normal: &Vector3<f64>) -> Vector3<f64> {
93 self.matrix * normal
94 }
95
96 pub fn normal_stress_on_plane(&self, normal: &Vector3<f64>) -> f64 {
98 let t = self.traction(normal);
99 normal.dot(&t)
100 }
101
102 pub fn shear_stress_on_plane(&self, normal: &Vector3<f64>) -> f64 {
104 let t = self.traction(normal);
105 let sn = self.normal_stress_on_plane(normal);
106 let tau_vec = t - sn * normal;
107 tau_vec.norm()
108 }
109
110 pub fn uniaxial(sigma: f64) -> Self {
112 Self::from_components(sigma, 0.0, 0.0, 0.0, 0.0, 0.0)
113 }
114
115 pub fn biaxial(sxx: f64, syy: f64) -> Self {
117 Self::from_components(sxx, syy, 0.0, 0.0, 0.0, 0.0)
118 }
119
120 pub fn pure_shear(txy: f64) -> Self {
122 Self::from_components(0.0, 0.0, 0.0, txy, 0.0, 0.0)
123 }
124}
125
126impl std::ops::Add for StressTensor {
127 type Output = StressTensor;
128 fn add(self, rhs: StressTensor) -> Self::Output {
129 StressTensor { matrix: self.matrix + rhs.matrix }
130 }
131}
132
133impl std::ops::Mul<f64> for StressTensor {
134 type Output = StressTensor;
135 fn mul(self, scalar: f64) -> Self::Output {
136 StressTensor { matrix: self.matrix * scalar }
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct StrainTensor {
144 pub matrix: Matrix3<f64>,
145}
146
147impl StrainTensor {
148 pub fn new(matrix: Matrix3<f64>) -> Self {
150 let sym = (matrix + matrix.transpose()) * 0.5;
151 Self { matrix: sym }
152 }
153
154 pub fn from_components(
156 exx: f64, eyy: f64, ezz: f64,
157 exy: f64, exz: f64, eyz: f64,
158 ) -> Self {
159 Self {
161 matrix: Matrix3::new(
162 exx, exy, exz,
163 exy, eyy, eyz,
164 exz, eyz, ezz,
165 ),
166 }
167 }
168
169 pub fn from_engineering(
171 exx: f64, eyy: f64, ezz: f64,
172 gamma_xy: f64, gamma_xz: f64, gamma_yz: f64,
173 ) -> Self {
174 Self::from_components(exx, eyy, ezz, gamma_xy / 2.0, gamma_xz / 2.0, gamma_yz / 2.0)
175 }
176
177 pub fn volumetric(&self) -> f64 {
179 self.matrix.trace()
180 }
181
182 pub fn deviatoric(&self) -> StrainTensor {
184 let ev = self.volumetric() / 3.0;
185 let dev = self.matrix - ev * Matrix3::identity();
186 StrainTensor { matrix: dev }
187 }
188
189 pub fn principal_strains(&self) -> Vector3<f64> {
191 let eig = self.matrix.symmetric_eigen();
192 let mut vals = eig.eigenvalues.data.0[0];
193 vals.sort_by(|a, b| b.partial_cmp(a).unwrap());
194 Vector3::new(vals[0], vals[1], vals[2])
195 }
196
197 pub fn max_shear_strain(&self) -> f64 {
199 let p = self.principal_strains();
200 p[0] - p[2]
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use approx::assert_relative_eq;
208
209 #[test]
210 fn test_stress_tensor_symmetry() {
211 let s = StressTensor::new(Matrix3::new(
212 10.0, 5.0, 3.0,
213 4.0, 20.0, 6.0,
214 7.0, 8.0, 30.0,
215 ));
216 assert_relative_eq!(s.matrix[(0, 1)], s.matrix[(1, 0)]);
217 assert_relative_eq!(s.matrix[(0, 2)], s.matrix[(2, 0)]);
218 assert_relative_eq!(s.matrix[(1, 2)], s.matrix[(2, 1)]);
219 }
220
221 #[test]
222 fn test_hydrostatic_stress() {
223 let s = StressTensor::from_components(30.0, 30.0, 30.0, 0.0, 0.0, 0.0);
224 assert_relative_eq!(s.hydrostatic(), 30.0);
225 }
226
227 #[test]
228 fn test_deviatoric_trace_zero() {
229 let s = StressTensor::from_components(10.0, 20.0, 30.0, 5.0, 0.0, 0.0);
230 let dev = s.deviatoric();
231 assert_relative_eq!(dev.matrix.trace(), 0.0, epsilon = 1e-10);
232 }
233
234 #[test]
235 fn test_principal_stresses_ordering() {
236 let s = StressTensor::from_components(30.0, 10.0, 20.0, 0.0, 0.0, 0.0);
237 let p = s.principal_stresses();
238 assert!(p[0] >= p[1]);
239 assert!(p[1] >= p[2]);
240 assert_relative_eq!(p[0], 30.0);
241 assert_relative_eq!(p[1], 20.0);
242 assert_relative_eq!(p[2], 10.0);
243 }
244
245 #[test]
246 fn test_max_shear() {
247 let s = StressTensor::from_components(100.0, 0.0, -50.0, 0.0, 0.0, 0.0);
248 assert_relative_eq!(s.max_shear(), 75.0);
249 }
250
251 #[test]
252 fn test_uniaxial_von_mises() {
253 let s = StressTensor::uniaxial(100.0);
254 assert_relative_eq!(s.von_mises(), 100.0);
255 }
256
257 #[test]
258 fn test_strain_volumetric() {
259 let e = StrainTensor::from_components(0.001, 0.002, 0.003, 0.0, 0.0, 0.0);
260 assert_relative_eq!(e.volumetric(), 0.006);
261 }
262
263 #[test]
264 fn test_strain_deviatoric_trace_zero() {
265 let e = StrainTensor::from_components(0.001, 0.002, 0.003, 0.0, 0.0, 0.0);
266 let dev = e.deviatoric();
267 assert_relative_eq!(dev.matrix.trace(), 0.0, epsilon = 1e-12);
268 }
269
270 #[test]
271 fn test_traction_vector() {
272 let s = StressTensor::from_components(10.0, 0.0, 0.0, 5.0, 0.0, 0.0);
273 let n = Vector3::new(1.0, 0.0, 0.0);
274 let t = s.traction(&n);
275 assert_relative_eq!(t[0], 10.0);
276 assert_relative_eq!(t[1], 5.0);
277 }
278}