use crate::math::Fix128;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LaminateStrengths {
pub xt: Fix128,
pub xc: Fix128,
pub yt: Fix128,
pub yc: Fix128,
pub s: Fix128,
}
impl LaminateStrengths {
#[must_use]
pub fn cfrp_ud() -> Self {
Self {
xt: Fix128::from_int(1500),
xc: Fix128::from_int(1500),
yt: Fix128::from_int(40),
yc: Fix128::from_int(246),
s: Fix128::from_int(68),
}
}
#[must_use]
pub fn gfrp_ud() -> Self {
Self {
xt: Fix128::from_int(1080),
xc: Fix128::from_int(620),
yt: Fix128::from_int(39),
yc: Fix128::from_int(128),
s: Fix128::from_int(89),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StressState {
pub sigma_1: Fix128,
pub sigma_2: Fix128,
pub tau_12: Fix128,
}
impl StressState {
#[must_use]
pub const fn zero() -> Self {
Self {
sigma_1: Fix128::ZERO,
sigma_2: Fix128::ZERO,
tau_12: Fix128::ZERO,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FailureMode {
Safe,
FibreTension,
FibreCompression,
MatrixTension,
MatrixCompression,
InterFibreA,
InterFibreB,
InterFibreC,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FailureCriterion {
TsaiWu,
TsaiHill,
Hashin,
Puck,
}
#[must_use]
pub fn tsai_wu_failure_index(strengths: LaminateStrengths, stress: StressState) -> Fix128 {
let s = strengths;
let f1 = Fix128::ONE / s.xt - Fix128::ONE / s.xc;
let f2 = Fix128::ONE / s.yt - Fix128::ONE / s.yc;
let f11 = Fix128::ONE / (s.xt * s.xc);
let f22 = Fix128::ONE / (s.yt * s.yc);
let f66 = Fix128::ONE / (s.s * s.s);
let f12 = -Fix128::from_ratio(1, 2) * (f11 * f22).sqrt();
let StressState {
sigma_1,
sigma_2,
tau_12,
} = stress;
f1 * sigma_1
+ f2 * sigma_2
+ f11 * sigma_1 * sigma_1
+ f22 * sigma_2 * sigma_2
+ f66 * tau_12 * tau_12
+ Fix128::from_int(2) * f12 * sigma_1 * sigma_2
}
#[must_use]
pub fn tsai_hill_failure_index(strengths: LaminateStrengths, stress: StressState) -> Fix128 {
let StressState {
sigma_1,
sigma_2,
tau_12,
} = stress;
let x = if sigma_1 >= Fix128::ZERO {
strengths.xt
} else {
strengths.xc
};
let y = if sigma_2 >= Fix128::ZERO {
strengths.yt
} else {
strengths.yc
};
let s = strengths.s;
let a = sigma_1 * sigma_1 / (x * x);
let b = sigma_2 * sigma_2 / (y * y);
let c = tau_12 * tau_12 / (s * s);
let cross = sigma_1 * sigma_2 / (x * x);
a - cross + b + c
}
#[must_use]
pub fn hashin_failure_mode(strengths: LaminateStrengths, stress: StressState) -> FailureMode {
let s = strengths;
let StressState {
sigma_1,
sigma_2,
tau_12,
} = stress;
if sigma_1 >= Fix128::ZERO {
let fi = sigma_1 * sigma_1 / (s.xt * s.xt) + tau_12 * tau_12 / (s.s * s.s);
if fi >= Fix128::ONE {
return FailureMode::FibreTension;
}
} else {
let fi = sigma_1 * sigma_1 / (s.xc * s.xc);
if fi >= Fix128::ONE {
return FailureMode::FibreCompression;
}
}
if sigma_2 >= Fix128::ZERO {
let fi = sigma_2 * sigma_2 / (s.yt * s.yt) + tau_12 * tau_12 / (s.s * s.s);
if fi >= Fix128::ONE {
return FailureMode::MatrixTension;
}
} else {
let yc_over_2s = s.yc / (Fix128::from_int(2) * s.s);
let coeff = (yc_over_2s * yc_over_2s - Fix128::ONE) * sigma_2 / s.yc;
let ratio = sigma_2 / (Fix128::from_int(2) * s.s);
let fi = ratio * ratio + coeff + tau_12 * tau_12 / (s.s * s.s);
if fi >= Fix128::ONE {
return FailureMode::MatrixCompression;
}
}
FailureMode::Safe
}
#[must_use]
pub fn puck_failure_mode(strengths: LaminateStrengths, stress: StressState) -> FailureMode {
if let m @ (FailureMode::FibreTension | FailureMode::FibreCompression) =
hashin_failure_mode(strengths, stress)
{
return m;
}
let StressState {
sigma_2, tau_12, ..
} = stress;
let s = strengths;
let ratio_normal = if sigma_2 >= Fix128::ZERO {
sigma_2 / s.yt
} else {
sigma_2.abs() / s.yc
};
let ratio_shear = tau_12.abs() / s.s;
if ratio_normal * ratio_normal + ratio_shear * ratio_shear < Fix128::ONE {
return FailureMode::Safe;
}
if sigma_2 >= Fix128::ZERO {
FailureMode::InterFibreA
} else if ratio_shear > ratio_normal {
FailureMode::InterFibreB
} else {
FailureMode::InterFibreC
}
}
#[must_use]
pub fn failure_index(
criterion: FailureCriterion,
strengths: LaminateStrengths,
stress: StressState,
) -> Fix128 {
match criterion {
FailureCriterion::TsaiWu => tsai_wu_failure_index(strengths, stress),
FailureCriterion::TsaiHill => tsai_hill_failure_index(strengths, stress),
FailureCriterion::Hashin => {
if hashin_failure_mode(strengths, stress) == FailureMode::Safe {
Fix128::ZERO
} else {
Fix128::ONE
}
}
FailureCriterion::Puck => {
if puck_failure_mode(strengths, stress) == FailureMode::Safe {
Fix128::ZERO
} else {
Fix128::ONE
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn stress(s1: i64, s2: i64, t12: i64) -> StressState {
StressState {
sigma_1: Fix128::from_int(s1),
sigma_2: Fix128::from_int(s2),
tau_12: Fix128::from_int(t12),
}
}
#[test]
fn tsai_wu_zero_stress_is_safe() {
let fi = tsai_wu_failure_index(LaminateStrengths::cfrp_ud(), StressState::zero());
assert!(fi < Fix128::ONE);
}
#[test]
fn tsai_wu_fibre_tension_at_xt_reaches_unity() {
let strengths = LaminateStrengths::cfrp_ud();
let fi = tsai_wu_failure_index(strengths, stress(1500, 0, 0));
let diff = fi - Fix128::ONE;
let mag = if diff < Fix128::ZERO { -diff } else { diff };
assert!(mag < Fix128::from_ratio(1, 100), "FI {fi:?} not near 1");
}
#[test]
fn tsai_hill_safe_below_all_limits() {
let fi = tsai_hill_failure_index(LaminateStrengths::cfrp_ud(), stress(100, 5, 5));
assert!(fi < Fix128::ONE);
}
#[test]
fn tsai_hill_flags_pure_shear_at_s() {
let strengths = LaminateStrengths::cfrp_ud();
let fi = tsai_hill_failure_index(strengths, stress(0, 0, 68));
let diff = fi - Fix128::ONE;
let mag = if diff < Fix128::ZERO { -diff } else { diff };
assert!(mag < Fix128::from_ratio(1, 100));
}
#[test]
fn hashin_reports_fibre_tension() {
let mode = hashin_failure_mode(LaminateStrengths::cfrp_ud(), stress(2000, 0, 0));
assert_eq!(mode, FailureMode::FibreTension);
}
#[test]
fn hashin_reports_fibre_compression() {
let mode = hashin_failure_mode(LaminateStrengths::cfrp_ud(), stress(-2000, 0, 0));
assert_eq!(mode, FailureMode::FibreCompression);
}
#[test]
fn hashin_reports_matrix_tension() {
let mode = hashin_failure_mode(LaminateStrengths::cfrp_ud(), stress(0, 100, 0));
assert_eq!(mode, FailureMode::MatrixTension);
}
#[test]
fn hashin_reports_matrix_compression() {
let mode = hashin_failure_mode(LaminateStrengths::cfrp_ud(), stress(0, -500, 0));
assert_eq!(mode, FailureMode::MatrixCompression);
}
#[test]
fn hashin_safe_under_low_stress() {
let mode = hashin_failure_mode(LaminateStrengths::cfrp_ud(), stress(500, 10, 20));
assert_eq!(mode, FailureMode::Safe);
}
#[test]
fn puck_reports_interfibre_a() {
let mode = puck_failure_mode(LaminateStrengths::cfrp_ud(), stress(0, 100, 0));
assert_eq!(mode, FailureMode::InterFibreA);
}
#[test]
fn puck_reports_interfibre_c_under_compression() {
let mode = puck_failure_mode(LaminateStrengths::cfrp_ud(), stress(0, -400, 20));
assert_eq!(mode, FailureMode::InterFibreC);
}
#[test]
fn dispatch_returns_expected_shape() {
let strengths = LaminateStrengths::cfrp_ud();
let s = stress(500, 20, 20);
let fi = failure_index(FailureCriterion::TsaiWu, strengths, s);
assert!(fi >= Fix128::ZERO);
let hashin_fi = failure_index(FailureCriterion::Hashin, strengths, s);
assert!(hashin_fi == Fix128::ZERO || hashin_fi == Fix128::ONE);
}
#[test]
fn presets_cfrp_and_gfrp_are_distinct() {
let cfrp = LaminateStrengths::cfrp_ud();
let gfrp = LaminateStrengths::gfrp_ud();
assert_ne!(cfrp.xt, gfrp.xt);
assert_ne!(cfrp.yt, gfrp.yt);
}
}