use nalgebra::{Matrix3, Vector3};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StressTensor {
pub matrix: Matrix3<f64>,
}
impl StressTensor {
pub fn new(matrix: Matrix3<f64>) -> Self {
let sym = (matrix + matrix.transpose()) * 0.5;
Self { matrix: sym }
}
pub fn from_components(
sxx: f64, syy: f64, szz: f64,
txy: f64, txz: f64, tyz: f64,
) -> Self {
Self {
matrix: Matrix3::new(
sxx, txy, txz,
txy, syy, tyz,
txz, tyz, szz,
),
}
}
pub fn hydrostatic(&self) -> f64 {
self.matrix.trace() / 3.0
}
pub fn deviatoric(&self) -> StressTensor {
let p = self.hydrostatic();
let dev = self.matrix - p * Matrix3::identity();
StressTensor { matrix: dev }
}
pub fn von_mises(&self) -> f64 {
let s = self.deviatoric().matrix;
((3.0 / 2.0) * (
s[(0, 0)] * s[(0, 0)] + s[(1, 1)] * s[(1, 1)] + s[(2, 2)] * s[(2, 2)]
+ 2.0 * (s[(0, 1)] * s[(0, 1)] + s[(0, 2)] * s[(0, 2)] + s[(1, 2)] * s[(1, 2)])
)).sqrt()
}
pub fn first_invariant(&self) -> f64 {
self.matrix.trace()
}
pub fn second_invariant(&self) -> f64 {
let m = &self.matrix;
m[(0, 0)] * m[(1, 1)] + m[(1, 1)] * m[(2, 2)] + m[(2, 2)] * m[(0, 0)]
- m[(0, 1)] * m[(0, 1)] - m[(1, 2)] * m[(1, 2)] - m[(0, 2)] * m[(0, 2)]
}
pub fn third_invariant(&self) -> f64 {
self.matrix.determinant()
}
pub fn principal_stresses(&self) -> Vector3<f64> {
let eig = self.matrix.symmetric_eigen();
let mut vals = eig.eigenvalues.data.0[0];
vals.sort_by(|a, b| b.partial_cmp(a).unwrap());
Vector3::new(vals[0], vals[1], vals[2])
}
pub fn max_shear(&self) -> f64 {
let p = self.principal_stresses();
(p[0] - p[2]) / 2.0
}
pub fn traction(&self, normal: &Vector3<f64>) -> Vector3<f64> {
self.matrix * normal
}
pub fn normal_stress_on_plane(&self, normal: &Vector3<f64>) -> f64 {
let t = self.traction(normal);
normal.dot(&t)
}
pub fn shear_stress_on_plane(&self, normal: &Vector3<f64>) -> f64 {
let t = self.traction(normal);
let sn = self.normal_stress_on_plane(normal);
let tau_vec = t - sn * normal;
tau_vec.norm()
}
pub fn uniaxial(sigma: f64) -> Self {
Self::from_components(sigma, 0.0, 0.0, 0.0, 0.0, 0.0)
}
pub fn biaxial(sxx: f64, syy: f64) -> Self {
Self::from_components(sxx, syy, 0.0, 0.0, 0.0, 0.0)
}
pub fn pure_shear(txy: f64) -> Self {
Self::from_components(0.0, 0.0, 0.0, txy, 0.0, 0.0)
}
}
impl std::ops::Add for StressTensor {
type Output = StressTensor;
fn add(self, rhs: StressTensor) -> Self::Output {
StressTensor { matrix: self.matrix + rhs.matrix }
}
}
impl std::ops::Mul<f64> for StressTensor {
type Output = StressTensor;
fn mul(self, scalar: f64) -> Self::Output {
StressTensor { matrix: self.matrix * scalar }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrainTensor {
pub matrix: Matrix3<f64>,
}
impl StrainTensor {
pub fn new(matrix: Matrix3<f64>) -> Self {
let sym = (matrix + matrix.transpose()) * 0.5;
Self { matrix: sym }
}
pub fn from_components(
exx: f64, eyy: f64, ezz: f64,
exy: f64, exz: f64, eyz: f64,
) -> Self {
Self {
matrix: Matrix3::new(
exx, exy, exz,
exy, eyy, eyz,
exz, eyz, ezz,
),
}
}
pub fn from_engineering(
exx: f64, eyy: f64, ezz: f64,
gamma_xy: f64, gamma_xz: f64, gamma_yz: f64,
) -> Self {
Self::from_components(exx, eyy, ezz, gamma_xy / 2.0, gamma_xz / 2.0, gamma_yz / 2.0)
}
pub fn volumetric(&self) -> f64 {
self.matrix.trace()
}
pub fn deviatoric(&self) -> StrainTensor {
let ev = self.volumetric() / 3.0;
let dev = self.matrix - ev * Matrix3::identity();
StrainTensor { matrix: dev }
}
pub fn principal_strains(&self) -> Vector3<f64> {
let eig = self.matrix.symmetric_eigen();
let mut vals = eig.eigenvalues.data.0[0];
vals.sort_by(|a, b| b.partial_cmp(a).unwrap());
Vector3::new(vals[0], vals[1], vals[2])
}
pub fn max_shear_strain(&self) -> f64 {
let p = self.principal_strains();
p[0] - p[2]
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_stress_tensor_symmetry() {
let s = StressTensor::new(Matrix3::new(
10.0, 5.0, 3.0,
4.0, 20.0, 6.0,
7.0, 8.0, 30.0,
));
assert_relative_eq!(s.matrix[(0, 1)], s.matrix[(1, 0)]);
assert_relative_eq!(s.matrix[(0, 2)], s.matrix[(2, 0)]);
assert_relative_eq!(s.matrix[(1, 2)], s.matrix[(2, 1)]);
}
#[test]
fn test_hydrostatic_stress() {
let s = StressTensor::from_components(30.0, 30.0, 30.0, 0.0, 0.0, 0.0);
assert_relative_eq!(s.hydrostatic(), 30.0);
}
#[test]
fn test_deviatoric_trace_zero() {
let s = StressTensor::from_components(10.0, 20.0, 30.0, 5.0, 0.0, 0.0);
let dev = s.deviatoric();
assert_relative_eq!(dev.matrix.trace(), 0.0, epsilon = 1e-10);
}
#[test]
fn test_principal_stresses_ordering() {
let s = StressTensor::from_components(30.0, 10.0, 20.0, 0.0, 0.0, 0.0);
let p = s.principal_stresses();
assert!(p[0] >= p[1]);
assert!(p[1] >= p[2]);
assert_relative_eq!(p[0], 30.0);
assert_relative_eq!(p[1], 20.0);
assert_relative_eq!(p[2], 10.0);
}
#[test]
fn test_max_shear() {
let s = StressTensor::from_components(100.0, 0.0, -50.0, 0.0, 0.0, 0.0);
assert_relative_eq!(s.max_shear(), 75.0);
}
#[test]
fn test_uniaxial_von_mises() {
let s = StressTensor::uniaxial(100.0);
assert_relative_eq!(s.von_mises(), 100.0);
}
#[test]
fn test_strain_volumetric() {
let e = StrainTensor::from_components(0.001, 0.002, 0.003, 0.0, 0.0, 0.0);
assert_relative_eq!(e.volumetric(), 0.006);
}
#[test]
fn test_strain_deviatoric_trace_zero() {
let e = StrainTensor::from_components(0.001, 0.002, 0.003, 0.0, 0.0, 0.0);
let dev = e.deviatoric();
assert_relative_eq!(dev.matrix.trace(), 0.0, epsilon = 1e-12);
}
#[test]
fn test_traction_vector() {
let s = StressTensor::from_components(10.0, 0.0, 0.0, 5.0, 0.0, 0.0);
let n = Vector3::new(1.0, 0.0, 0.0);
let t = s.traction(&n);
assert_relative_eq!(t[0], 10.0);
assert_relative_eq!(t[1], 5.0);
}
}