#[allow(unused_imports)]
use super::functions::*;
use super::functions::{normal_quantile, standard_normal_cdf};
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CoffinManson {
pub epsilon_f: f64,
pub c: f64,
}
#[allow(dead_code)]
impl CoffinManson {
pub fn new(epsilon_f: f64, c: f64) -> Self {
Self { epsilon_f, c }
}
pub fn plastic_strain_amplitude(&self, two_n: f64) -> f64 {
self.epsilon_f * two_n.powf(self.c)
}
pub fn reversals_to_failure(&self, plastic_strain_amp: f64) -> f64 {
(plastic_strain_amp / self.epsilon_f).powf(1.0 / self.c)
}
pub fn cycles_to_failure(&self, plastic_strain_amp: f64) -> f64 {
self.reversals_to_failure(plastic_strain_amp) / 2.0
}
pub fn transition_life(&self, sigma_f: f64, b: f64, e_modulus: f64) -> f64 {
let ratio = self.epsilon_f * e_modulus / sigma_f;
ratio.powf(1.0 / (b - self.c))
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct MinerRule {
pub damages: Vec<f64>,
}
#[allow(dead_code)]
impl MinerRule {
pub fn new(damages: Vec<f64>) -> Self {
Self { damages }
}
pub fn total_damage(&self) -> f64 {
self.damages.iter().sum()
}
pub fn is_failed(&self) -> bool {
self.total_damage() >= 1.0
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct FatigueSurface {
pub a: f64,
pub b: f64,
pub sigma_ult: f64,
pub sigma_endurance: f64,
}
#[allow(dead_code)]
impl FatigueSurface {
pub fn new(a: f64, b: f64, sigma_ult: f64, sigma_endurance: f64) -> Self {
Self {
a,
b,
sigma_ult,
sigma_endurance,
}
}
pub fn damage_index(&self, sigma_a1: f64, sigma_a2: f64) -> f64 {
let se = self.sigma_endurance;
if se <= 0.0 {
return f64::INFINITY;
}
let r1 = sigma_a1 / se;
let r2 = sigma_a2 / se;
r1 * r1 + self.a * r1 * r2 + self.b * r2 * r2
}
pub fn is_failed(&self, sigma_a1: f64, sigma_a2: f64) -> bool {
self.damage_index(sigma_a1, sigma_a2) >= 1.0
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct FatigueLifePredictor {
pub sn: BasquinCurve,
pub sigma_ult: f64,
}
#[allow(dead_code)]
impl FatigueLifePredictor {
pub fn new(sn: BasquinCurve, sigma_ult: f64) -> Self {
Self { sn, sigma_ult }
}
pub fn predict_cycles(&self, sigma_a: f64, sigma_m: f64) -> f64 {
let denom = 1.0 - sigma_m / self.sigma_ult;
if denom <= 0.0 {
return 0.0;
}
let sigma_ar = sigma_a / denom;
self.sn.cycles_to_failure(sigma_ar)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MorrowCorrection {
pub sigma_f: f64,
pub b: f64,
pub e_modulus: f64,
}
#[allow(dead_code)]
impl MorrowCorrection {
pub fn new(sigma_f: f64, b: f64, e_modulus: f64) -> Self {
Self {
sigma_f,
b,
e_modulus,
}
}
pub fn corrected_strain_amplitude(&self, mean_stress: f64, two_n: f64) -> f64 {
((self.sigma_f - mean_stress) / self.e_modulus) * two_n.powf(self.b)
}
pub fn effective_sigma_f(&self, mean_stress: f64) -> f64 {
self.sigma_f - mean_stress
}
pub fn cycles_to_failure(&self, strain_amplitude: f64, mean_stress: f64) -> f64 {
let sigma_eff = self.effective_sigma_f(mean_stress);
if sigma_eff <= 0.0 {
return 0.0;
}
let two_n = (strain_amplitude * self.e_modulus / sigma_eff).powf(1.0 / self.b);
two_n / 2.0
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SoderbergDiagram {
pub sigma_yield: f64,
pub sigma_endurance: f64,
}
#[allow(dead_code)]
impl SoderbergDiagram {
pub fn new(sigma_yield: f64, sigma_endurance: f64) -> Self {
Self {
sigma_yield,
sigma_endurance,
}
}
pub fn soderberg_allowed_amplitude(&self, mean_stress: f64) -> f64 {
(self.sigma_endurance * (1.0 - mean_stress / self.sigma_yield)).max(0.0)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MinerWithCrit {
pub damage: f64,
pub d_crit: f64,
}
#[allow(dead_code)]
impl MinerWithCrit {
pub fn new(d_crit: f64) -> Self {
Self {
damage: 0.0,
d_crit,
}
}
pub fn add_block(&mut self, n_applied: f64, n_failure: f64) {
if n_failure > 0.0 {
self.damage += n_applied / n_failure;
}
}
pub fn is_failed(&self) -> bool {
self.damage >= self.d_crit
}
pub fn remaining_life_fraction(&self) -> f64 {
((self.d_crit - self.damage) / self.d_crit).max(0.0)
}
pub fn cycles_to_failure(&self, n_f: f64) -> f64 {
if self.damage >= self.d_crit {
return 0.0;
}
(self.d_crit - self.damage) * n_f
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RambergOsgood {
pub e_modulus: f64,
pub k_prime: f64,
pub n_prime: f64,
}
#[allow(dead_code)]
impl RambergOsgood {
pub fn new(e_modulus: f64, k_prime: f64, n_prime: f64) -> Self {
Self {
e_modulus,
k_prime,
n_prime,
}
}
pub fn strain(&self, sigma: f64) -> f64 {
sigma / self.e_modulus
+ (sigma.abs() / self.k_prime).powf(1.0 / self.n_prime) * sigma.signum()
}
pub fn elastic_strain(&self, sigma: f64) -> f64 {
sigma / self.e_modulus
}
pub fn plastic_strain(&self, sigma: f64) -> f64 {
(sigma.abs() / self.k_prime).powf(1.0 / self.n_prime) * sigma.signum()
}
pub fn stress_from_strain(&self, epsilon: f64, max_iter: usize) -> f64 {
if epsilon.abs() < 1e-30 {
return 0.0;
}
let sign = epsilon.signum();
let eps_abs = epsilon.abs();
let sigma_max = eps_abs * self.e_modulus * 10.0;
let mut lo = 0.0_f64;
let mut hi = sigma_max;
for _ in 0..max_iter {
let mid = 0.5 * (lo + hi);
let eps_mid = self.strain(mid * sign).abs();
if eps_mid < eps_abs {
lo = mid;
} else {
hi = mid;
}
if (hi - lo) / sigma_max < 1e-12 {
break;
}
}
sign * 0.5 * (lo + hi)
}
pub fn plastic_strain_range(&self, delta_sigma: f64) -> f64 {
2.0 * (delta_sigma / (2.0 * self.k_prime)).powf(1.0 / self.n_prime)
}
pub fn energy_per_cycle(&self, delta_sigma: f64) -> f64 {
let dep = self.plastic_strain_range(delta_sigma);
delta_sigma * dep * 2.0 * self.n_prime / (self.n_prime + 1.0)
}
}
#[derive(Debug, Clone)]
pub struct PalmgrenMinor {
pub damage: f64,
}
impl PalmgrenMinor {
pub fn new() -> Self {
Self { damage: 0.0 }
}
pub fn apply_cycle_block(&mut self, n_applied: f64, n_failure: f64) {
self.damage += n_applied / n_failure;
}
pub fn total_damage(&self) -> f64 {
self.damage
}
pub fn is_failed(&self) -> bool {
self.damage >= 1.0
}
pub fn reset(&mut self) {
self.damage = 0.0;
}
#[allow(dead_code)]
pub fn remaining_life(&self) -> f64 {
(1.0 - self.damage).clamp(0.0, 1.0)
}
#[allow(dead_code)]
pub fn apply_spectrum(&mut self, blocks: &[(f64, f64)]) {
for &(n_applied, n_failure) in blocks {
self.apply_cycle_block(n_applied, n_failure);
}
}
#[allow(dead_code)]
pub fn remaining_cycles(&self, n_failure_at_stress: f64) -> f64 {
if self.damage >= 1.0 {
return 0.0;
}
(1.0 - self.damage) * n_failure_at_stress
}
}
#[allow(dead_code)]
pub struct BasquinStressLife {
pub sigma_f_prime: f64,
pub b_exp: f64,
pub endurance_limit: f64,
}
impl BasquinStressLife {
pub fn new(sigma_f_prime: f64, b_exp: f64, endurance_limit: f64) -> Self {
Self {
sigma_f_prime,
b_exp,
endurance_limit,
}
}
pub fn stress_amplitude(&self, n_reversals: f64) -> f64 {
self.sigma_f_prime * n_reversals.powf(self.b_exp)
}
pub fn cycles_to_failure(&self, sigma_a: f64) -> f64 {
if sigma_a <= self.endurance_limit {
return f64::INFINITY;
}
let two_n_f = (sigma_a / self.sigma_f_prime).powf(1.0 / self.b_exp);
two_n_f / 2.0
}
pub fn damage_per_cycle(&self, sigma_a: f64) -> f64 {
let n_f = self.cycles_to_failure(sigma_a);
if n_f.is_infinite() { 0.0 } else { 1.0 / n_f }
}
pub fn endurance_ratio(&self) -> f64 {
self.endurance_limit / self.sigma_f_prime
}
pub fn transition_cycles(&self) -> f64 {
self.cycles_to_failure(self.endurance_limit * 1.000_000_001)
}
pub fn cycles_with_safety_factor(&self, sigma_a: f64, safety_factor: f64) -> f64 {
self.cycles_to_failure(sigma_a * safety_factor)
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub struct RainflowCycle {
pub range: f64,
pub mean: f64,
pub count: f64,
}
impl RainflowCycle {
pub fn amplitude(&self) -> f64 {
self.range / 2.0
}
pub fn stress_ratio(&self) -> f64 {
let s_min = self.mean - self.amplitude();
let s_max = self.mean + self.amplitude();
if s_max.abs() > 1e-30 {
s_min / s_max
} else {
0.0
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct BasquinCurve {
pub a: f64,
pub b_exp: f64,
pub endurance_limit: f64,
}
#[allow(dead_code)]
impl BasquinCurve {
pub fn new(a: f64, b_exp: f64, endurance_limit: f64) -> Self {
Self {
a,
b_exp,
endurance_limit,
}
}
pub fn from_two_points(
n1: f64,
sigma1: f64,
n2: f64,
sigma2: f64,
endurance_limit: f64,
) -> Self {
let b_exp = (sigma1 / sigma2).ln() / (n1 / n2).ln();
let a = sigma1 / n1.powf(b_exp);
Self {
a,
b_exp,
endurance_limit,
}
}
pub fn stress_at_n(&self, n: f64) -> f64 {
self.a * n.powf(self.b_exp)
}
pub fn cycles_to_failure(&self, stress_amplitude: f64) -> f64 {
if stress_amplitude <= self.endurance_limit {
return f64::INFINITY;
}
(stress_amplitude / self.a).powf(1.0 / self.b_exp)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct SNcurve {
pub sigma_f: f64,
pub b: f64,
pub epsilon_f: f64,
pub c: f64,
pub e_modulus: f64,
}
impl SNcurve {
pub fn new(sigma_f: f64, b: f64, epsilon_f: f64, c: f64, e_modulus: f64) -> Self {
Self {
sigma_f,
b,
epsilon_f,
c,
e_modulus,
}
}
pub fn elastic_strain_amplitude(&self, n_reversals: f64) -> f64 {
(self.sigma_f / self.e_modulus) * n_reversals.powf(self.b)
}
pub fn plastic_strain_amplitude(&self, n_reversals: f64) -> f64 {
self.epsilon_f * n_reversals.powf(self.c)
}
pub fn total_strain_amplitude(&self, n_reversals: f64) -> f64 {
self.elastic_strain_amplitude(n_reversals) + self.plastic_strain_amplitude(n_reversals)
}
pub fn cycles_to_failure_strain(&self, strain_amplitude: f64) -> f64 {
let mut lo = 2.0_f64;
let mut hi = 2.0e12_f64;
let f_lo = self.total_strain_amplitude(lo) - strain_amplitude;
let f_hi = self.total_strain_amplitude(hi) - strain_amplitude;
if f_lo <= 0.0 {
return lo / 2.0;
}
if f_hi >= 0.0 {
return hi / 2.0;
}
for _ in 0..100 {
let mid = (lo + hi) / 2.0;
let f_mid = self.total_strain_amplitude(mid) - strain_amplitude;
if f_mid > 0.0 {
lo = mid;
} else {
hi = mid;
}
if (hi - lo) / hi < 1.0e-12 {
break;
}
}
(lo + hi) / 2.0 / 2.0
}
pub fn stress_amplitude_from_n(&self, n_cycles: f64) -> f64 {
let two_n = 2.0 * n_cycles;
self.sigma_f * two_n.powf(self.b)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct NeuberRule {
pub kt: f64,
pub e_modulus: f64,
pub k_prime: f64,
pub n_prime: f64,
}
#[allow(dead_code)]
impl NeuberRule {
pub fn new(kt: f64, e_modulus: f64, k_prime: f64, n_prime: f64) -> Self {
Self {
kt,
e_modulus,
k_prime,
n_prime,
}
}
pub fn neuber_product(&self, sigma_nom: f64) -> f64 {
(self.kt * sigma_nom).powi(2) / self.e_modulus
}
pub fn ramberg_osgood_strain(&self, sigma_local: f64) -> f64 {
sigma_local / self.e_modulus + (sigma_local / self.k_prime).powf(1.0 / self.n_prime)
}
pub fn local_stress(&self, sigma_nom: f64, max_iter: usize) -> f64 {
let target = self.neuber_product(sigma_nom);
if target <= 0.0 {
return 0.0;
}
let sigma_el = self.kt * sigma_nom.abs();
let mut lo = 0.0_f64;
let mut hi = sigma_el * 3.0;
for _ in 0..max_iter {
let mid = 0.5 * (lo + hi);
let eps_mid = self.ramberg_osgood_strain(mid);
let prod = mid * eps_mid;
if prod < target {
lo = mid;
} else {
hi = mid;
}
if (hi - lo) / sigma_el.max(1.0) < 1e-12 {
break;
}
}
0.5 * (lo + hi)
}
pub fn local_strain(&self, sigma_local: f64) -> f64 {
self.ramberg_osgood_strain(sigma_local)
}
pub fn cyclic_local_stress_strain(&self, sigma_nom_amp: f64, max_iter: usize) -> (f64, f64) {
let target = self.neuber_product(sigma_nom_amp);
if target <= 0.0 {
return (0.0, 0.0);
}
let sigma_el = self.kt * sigma_nom_amp;
let mut lo = 0.0_f64;
let mut hi = sigma_el * 3.0;
for _ in 0..max_iter {
let mid = 0.5 * (lo + hi);
let eps_mid = self.ramberg_osgood_strain(mid);
let prod = mid * eps_mid;
if prod < target {
lo = mid;
} else {
hi = mid;
}
if (hi - lo) / sigma_el.max(1.0) < 1e-12 {
break;
}
}
let sigma_a = 0.5 * (lo + hi);
(sigma_a, self.ramberg_osgood_strain(sigma_a))
}
}
#[allow(dead_code)]
pub struct CoffinMansonLcf {
pub epsilon_f_prime: f64,
pub c_exp: f64,
}
impl CoffinMansonLcf {
pub fn new(epsilon_f_prime: f64, c_exp: f64) -> Self {
Self {
epsilon_f_prime,
c_exp,
}
}
pub fn plastic_strain_amplitude(&self, n_reversals: f64) -> f64 {
self.epsilon_f_prime * n_reversals.powf(self.c_exp)
}
pub fn reversals_to_failure(&self, delta_epsilon_p_half: f64) -> f64 {
(delta_epsilon_p_half / self.epsilon_f_prime).powf(1.0 / self.c_exp)
}
pub fn cycles_to_failure(&self, delta_epsilon_p_half: f64) -> f64 {
self.reversals_to_failure(delta_epsilon_p_half) / 2.0
}
pub fn transition_reversals(&self, sigma_f: f64, e_modulus: f64, b_exp: f64) -> f64 {
let ratio = self.epsilon_f_prime * e_modulus / sigma_f;
ratio.powf(1.0 / (b_exp - self.c_exp))
}
pub fn damage_per_cycle(&self, delta_epsilon_p_half: f64) -> f64 {
let n_f = self.cycles_to_failure(delta_epsilon_p_half);
if n_f > 0.0 && n_f.is_finite() {
1.0 / n_f
} else {
0.0
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct GoodmanDiagramNew {
pub sigma_ult: f64,
pub sigma_endurance: f64,
}
#[allow(dead_code)]
impl GoodmanDiagramNew {
pub fn new(sigma_ult: f64, sigma_endurance: f64) -> Self {
Self {
sigma_ult,
sigma_endurance,
}
}
pub fn goodman_allowed_amplitude(&self, mean_stress: f64) -> f64 {
(self.sigma_endurance * (1.0 - mean_stress / self.sigma_ult)).max(0.0)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SwtParameter {
pub sigma_f: f64,
pub b: f64,
pub epsilon_f: f64,
pub c: f64,
pub e_modulus: f64,
}
#[allow(dead_code)]
impl SwtParameter {
pub fn new(sigma_f: f64, b: f64, epsilon_f: f64, c: f64, e_modulus: f64) -> Self {
Self {
sigma_f,
b,
epsilon_f,
c,
e_modulus,
}
}
pub fn compute(max_stress: f64, strain_amplitude: f64) -> f64 {
if max_stress <= 0.0 {
return 0.0;
}
max_stress * strain_amplitude
}
pub fn material_swt(&self, two_n: f64) -> f64 {
let term1 = self.sigma_f.powi(2) / self.e_modulus * two_n.powf(2.0 * self.b);
let term2 = self.sigma_f * self.epsilon_f * two_n.powf(self.b + self.c);
term1 + term2
}
pub fn cycles_to_failure(&self, swt_value: f64) -> f64 {
if swt_value <= 0.0 {
return f64::INFINITY;
}
let mut lo = 2.0_f64;
let mut hi = 2.0e12_f64;
for _ in 0..100 {
let mid = (lo + hi) / 2.0;
let swt_mid = self.material_swt(mid);
if swt_mid > swt_value {
lo = mid;
} else {
hi = mid;
}
if (hi - lo) / hi < 1e-12 {
break;
}
}
(lo + hi) / 4.0
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SNScatterBand {
pub sn: BasquinCurve,
pub t_n: f64,
}
#[allow(dead_code)]
impl SNScatterBand {
pub fn new(sn: BasquinCurve, t_n: f64) -> Self {
Self { sn, t_n }
}
pub fn log_std(&self) -> f64 {
self.t_n.log10() / (2.0 * 1.2816)
}
pub fn life_at_survival(&self, sigma_amp: f64, p_survival: f64) -> f64 {
if sigma_amp <= self.sn.endurance_limit {
return f64::INFINITY;
}
let n_median = self.sn.cycles_to_failure(sigma_amp);
if !n_median.is_finite() {
return f64::INFINITY;
}
let log_n_med = n_median.log10();
let s = self.log_std();
let z = normal_quantile(1.0 - p_survival);
10.0_f64.powf(log_n_med + z * s)
}
pub fn survival_probability(&self, sigma_amp: f64, n_cycles: f64) -> f64 {
if sigma_amp <= self.sn.endurance_limit {
return 1.0;
}
let n_median = self.sn.cycles_to_failure(sigma_amp);
if !n_median.is_finite() {
return 1.0;
}
let s = self.log_std();
if s <= 0.0 {
return if n_cycles < n_median { 1.0 } else { 0.0 };
}
let z = (n_cycles.log10() - n_median.log10()) / s;
standard_normal_cdf(z)
}
}
#[allow(dead_code)]
pub struct CycleDamageAccumulator {
pub damage: f64,
pub d_crit: f64,
pub history: Vec<(f64, f64, f64)>,
}
impl CycleDamageAccumulator {
pub fn new(d_crit: f64) -> Self {
Self {
damage: 0.0,
d_crit,
history: Vec::new(),
}
}
pub fn apply_cycles(&mut self, n_applied: f64, sigma_a: f64, sn: &BasquinStressLife) {
let n_f = sn.cycles_to_failure(sigma_a);
let d = if n_f.is_infinite() {
0.0
} else {
n_applied / n_f
};
self.damage += d;
self.history.push((sigma_a, n_f, n_applied));
}
pub fn apply_rainflow(&mut self, cycles: &[RainflowCycle], sn: &BasquinStressLife) {
for cyc in cycles {
self.apply_cycles(cyc.count, cyc.amplitude(), sn);
}
}
pub fn is_failed(&self) -> bool {
self.damage >= self.d_crit
}
pub fn remaining_life_fraction(&self) -> f64 {
((self.d_crit - self.damage) / self.d_crit).max(0.0)
}
pub fn remaining_cycles(&self, sigma_a: f64, sn: &BasquinStressLife) -> f64 {
let n_f = sn.cycles_to_failure(sigma_a);
if n_f.is_infinite() {
return f64::INFINITY;
}
let remaining_d = (self.d_crit - self.damage).max(0.0);
remaining_d * n_f
}
pub fn reset(&mut self) {
self.damage = 0.0;
self.history.clear();
}
pub fn block_count(&self) -> usize {
self.history.len()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ParisLaw {
pub c: f64,
pub m: f64,
pub delta_k_threshold: f64,
pub k_fracture: f64,
}
#[allow(dead_code)]
impl ParisLaw {
pub fn new(c: f64, m: f64, delta_k_threshold: f64, k_fracture: f64) -> Self {
Self {
c,
m,
delta_k_threshold,
k_fracture,
}
}
pub fn crack_growth_rate(&self, delta_k: f64) -> f64 {
if delta_k < self.delta_k_threshold {
return 0.0;
}
if delta_k >= self.k_fracture {
return f64::INFINITY;
}
self.c * delta_k.powf(self.m)
}
pub fn cycles_to_grow(
&self,
a_initial: f64,
a_final: f64,
delta_k_fn: &impl Fn(f64) -> f64,
da_step: f64,
) -> Option<f64> {
let mut a = a_initial;
let mut n_cycles = 0.0_f64;
while a < a_final {
let delta_k = delta_k_fn(a);
let da_dn = self.crack_growth_rate(delta_k);
if da_dn.is_infinite() || da_dn <= 0.0 {
return if da_dn.is_infinite() {
None
} else {
Some(f64::INFINITY)
};
}
let dn = da_step / da_dn;
n_cycles += dn;
a += da_step;
}
Some(n_cycles)
}
pub fn delta_k_center_crack(delta_sigma: f64, a: f64, y: f64) -> f64 {
delta_sigma * (std::f64::consts::PI * a).sqrt() * y
}
pub fn nasgro_rate(&self, delta_k: f64, r_ratio: f64, crack_opening_f: f64) -> f64 {
let effective_dk = delta_k * (1.0 - crack_opening_f) / (1.0 - r_ratio).max(0.01);
self.crack_growth_rate(effective_dk)
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct GoodmanDiagram {
pub ultimate_strength: f64,
pub yield_strength: f64,
}
impl GoodmanDiagram {
pub fn new(uts: f64, ys: f64) -> Self {
Self {
ultimate_strength: uts,
yield_strength: ys,
}
}
pub fn allowable_amplitude(
&self,
mean_stress: f64,
endurance: f64,
factor_of_safety: f64,
) -> f64 {
endurance * (1.0 - mean_stress / self.ultimate_strength) / factor_of_safety
}
pub fn soderberg_amplitude(&self, mean_stress: f64, endurance: f64) -> f64 {
endurance * (1.0 - mean_stress / self.yield_strength)
}
pub fn is_safe(&self, mean_stress: f64, amplitude: f64, endurance: f64) -> bool {
amplitude / endurance + mean_stress / self.ultimate_strength <= 1.0
}
#[allow(dead_code)]
pub fn equivalent_amplitude(&self, mean_stress: f64, amplitude: f64) -> f64 {
let denom = 1.0 - mean_stress / self.ultimate_strength;
if denom <= 0.0 {
return f64::INFINITY;
}
amplitude / denom
}
#[allow(dead_code)]
pub fn factor_of_safety(&self, mean_stress: f64, amplitude: f64, endurance: f64) -> f64 {
let ratio = amplitude / endurance + mean_stress / self.ultimate_strength;
if ratio <= 0.0 {
return f64::INFINITY;
}
1.0 / ratio
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DamageToleranceResult {
pub a_initial: f64,
pub a_critical: f64,
pub inspection_interval: f64,
pub cycles_to_critical: Option<f64>,
pub safety_factor: f64,
}
#[allow(dead_code)]
impl DamageToleranceResult {
pub fn is_safe(&self) -> bool {
if let Some(cycles) = self.cycles_to_critical {
cycles > self.inspection_interval * self.safety_factor
} else {
false
}
}
pub fn remaining_life(&self, current_cycles: f64) -> f64 {
match self.cycles_to_critical {
Some(total) => (total - current_cycles).max(0.0),
None => 0.0,
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct GerberDiagram {
pub sigma_ult: f64,
pub sigma_endurance: f64,
}
#[allow(dead_code)]
impl GerberDiagram {
pub fn new(sigma_ult: f64, sigma_endurance: f64) -> Self {
Self {
sigma_ult,
sigma_endurance,
}
}
pub fn gerber_allowed_amplitude(&self, mean_stress: f64) -> f64 {
let ratio = mean_stress / self.sigma_ult;
(self.sigma_endurance * (1.0 - ratio * ratio)).max(0.0)
}
}