use serde::{Deserialize, Serialize};
use crate::tensor::StressTensor;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MohrCircle {
pub center: f64,
pub radius: f64,
pub sigma1: f64,
pub sigma2: f64,
pub tau_max: f64,
pub theta_p: f64,
}
impl MohrCircle {
pub fn from_2d(sxx: f64, syy: f64, txy: f64) -> Self {
let center = (sxx + syy) / 2.0;
let radius = (((sxx - syy) / 2.0).powi(2) + txy.powi(2)).sqrt();
let sigma1 = center + radius;
let sigma2 = center - radius;
let tau_max = radius;
let theta_p = 0.5 * (2.0 * txy).atan2(sxx - syy);
Self { center, radius, sigma1, sigma2, tau_max, theta_p }
}
pub fn from_stress_tensor_2d(stress: &StressTensor) -> Self {
Self::from_2d(
stress.matrix[(0, 0)],
stress.matrix[(1, 1)],
stress.matrix[(0, 1)],
)
}
pub fn from_3d(stress: &StressTensor) -> [MohrCircle; 3] {
let p = stress.principal_stresses();
let s1 = p[0]; let s2 = p[1];
let s3 = p[2]; [
MohrCircle::from_2d(s1, s2, 0.0), MohrCircle::from_2d(s2, s3, 0.0), MohrCircle::from_2d(s1, s3, 0.0), ]
}
pub fn stress_at_angle(&self, theta: f64) -> (f64, f64) {
let sn = self.center + self.radius * (2.0 * theta).cos();
let tn = -self.radius * (2.0 * theta).sin(); (sn, tn)
}
pub fn absolute_max_shear(stress: &StressTensor) -> f64 {
let p = stress.principal_stresses();
(p[0] - p[2]) / 2.0
}
}
pub fn stress_transform_2d(sxx: f64, syy: f64, txy: f64, theta: f64) -> (f64, f64, f64) {
let c = theta.cos();
let s = theta.sin();
let c2 = c * c;
let s2 = s * s;
let cs = c * s;
let sx_x = sxx * c2 + syy * s2 + 2.0 * txy * cs;
let sy_y = sxx * s2 + syy * c2 - 2.0 * txy * cs;
let tx_y = -(sxx - syy) * cs + txy * (c2 - s2);
(sx_x, sy_y, tx_y)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_mohr_circle_pure_shear() {
let mc = MohrCircle::from_2d(0.0, 0.0, 50.0);
assert_relative_eq!(mc.center, 0.0);
assert_relative_eq!(mc.radius, 50.0);
assert_relative_eq!(mc.sigma1, 50.0);
assert_relative_eq!(mc.sigma2, -50.0);
assert_relative_eq!(mc.tau_max, 50.0);
}
#[test]
fn test_mohr_circle_uniaxial() {
let mc = MohrCircle::from_2d(100.0, 0.0, 0.0);
assert_relative_eq!(mc.center, 50.0);
assert_relative_eq!(mc.radius, 50.0);
assert_relative_eq!(mc.sigma1, 100.0);
assert_relative_eq!(mc.sigma2, 0.0);
}
#[test]
fn test_mohr_circle_biaxial() {
let mc = MohrCircle::from_2d(80.0, -40.0, 30.0);
let c = (80.0 + (-40.0)) / 2.0;
assert_relative_eq!(mc.center, c);
let r = ((80.0_f64 - (-40.0_f64)) / 2.0).hypot(30.0);
assert_relative_eq!(mc.radius, r);
assert_relative_eq!(mc.sigma1, c + r);
assert_relative_eq!(mc.sigma2, c - r);
}
#[test]
fn test_stress_transform_principal_angle() {
let mc = MohrCircle::from_2d(80.0, -40.0, 30.0);
let (_sx, _sy, txy) = stress_transform_2d(80.0, -40.0, 30.0, mc.theta_p);
assert_relative_eq!(txy, 0.0, epsilon = 1e-10);
}
#[test]
fn test_mohr_3d() {
let stress = StressTensor::from_components(100.0, 50.0, -20.0, 0.0, 0.0, 0.0);
let circles = MohrCircle::from_3d(&stress);
assert_relative_eq!(circles[2].sigma1, 100.0);
assert_relative_eq!(circles[2].sigma2, -20.0);
assert_relative_eq!(circles[2].radius, 60.0);
}
#[test]
fn test_stress_at_45_degrees() {
let mc = MohrCircle::from_2d(100.0, 0.0, 0.0);
let (sn, tn) = mc.stress_at_angle(std::f64::consts::FRAC_PI_4);
assert_relative_eq!(sn, 50.0);
assert_relative_eq!(tn.abs(), 50.0);
}
#[test]
fn test_absolute_max_shear() {
let stress = StressTensor::from_components(100.0, 50.0, -20.0, 0.0, 0.0, 0.0);
let tau = MohrCircle::absolute_max_shear(&stress);
assert_relative_eq!(tau, 60.0);
}
}