qualia_core_db/specialized_libs/engineering_analysis/
survival.rs1#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct StressState {
11 pub von_mises: f64,
13 pub principal: [f64; 3],
15 pub max_shear: f64,
17 pub hydrostatic: f64,
19}
20
21fn principal_stresses(t: &[[f64; 3]; 3]) -> [f64; 3] {
27 let a = [
28 t[0][0], t[0][1], t[0][2], t[1][0], t[1][1], t[1][2], t[2][0], t[2][1], t[2][2],
29 ];
30 crate::solvers::linear_algebra::eigen::symmetric_eigen_3x3(&a)
31}
32
33pub fn cauchy_stress_analysis(tensor: &[[f64; 3]; 3]) -> StressState {
36 let (sxx, syy, szz) = (tensor[0][0], tensor[1][1], tensor[2][2]);
37 let (txy, tyz, tzx) = (tensor[0][1], tensor[1][2], tensor[2][0]);
38 let von_mises = (0.5 * ((sxx - syy).powi(2) + (syy - szz).powi(2) + (szz - sxx).powi(2))
39 + 3.0 * (txy * txy + tyz * tyz + tzx * tzx))
40 .sqrt();
41 let principal = principal_stresses(tensor);
42 StressState {
43 von_mises,
44 principal,
45 max_shear: (principal[0] - principal[2]) / 2.0,
46 hydrostatic: (sxx + syy + szz) / 3.0,
47 }
48}
49
50pub fn drag_force(
53 air_density_kg_m3: f64,
54 velocity_m_s: f64,
55 drag_coefficient: f64,
56 area_m2: f64,
57) -> f64 {
58 0.5 * air_density_kg_m3 * velocity_m_s * velocity_m_s * drag_coefficient * area_m2
59}
60
61pub fn reynolds_number(
63 density: f64,
64 velocity: f64,
65 char_length_m: f64,
66 dynamic_viscosity: f64,
67) -> f64 {
68 if dynamic_viscosity == 0.0 {
69 return f64::INFINITY;
70 }
71 density * velocity * char_length_m / dynamic_viscosity
72}
73
74pub fn fatigue_cycles_basquin(
79 stress_amplitude: f64,
80 fatigue_strength_coeff: f64,
81 fatigue_strength_exponent: f64,
82) -> f64 {
83 if stress_amplitude <= 0.0 || fatigue_strength_coeff <= 0.0 || fatigue_strength_exponent == 0.0
84 {
85 return f64::INFINITY;
86 }
87 0.5 * (stress_amplitude / fatigue_strength_coeff).powf(1.0 / fatigue_strength_exponent)
88}
89
90pub fn miner_cumulative_damage(blocks: &[(f64, f64)]) -> f64 {
93 let mut d = 0.0;
94 for &(applied, allowable) in blocks {
95 if allowable > 0.0 {
96 d += applied / allowable;
97 }
98 }
99 d
100}
101
102#[cfg(test)]
103mod survival_engineering_tests {
104 use super::*;
105
106 #[test]
107 fn uniaxial_stress_state() {
108 let t = [[100.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]];
110 let s = cauchy_stress_analysis(&t);
111 assert!((s.von_mises - 100.0).abs() < 1e-6);
112 assert!((s.principal[0] - 100.0).abs() < 1e-6 && s.principal[2].abs() < 1e-6);
113 assert!((s.max_shear - 50.0).abs() < 1e-6);
114 assert!((s.hydrostatic - 100.0 / 3.0).abs() < 1e-6);
115 }
116
117 #[test]
118 fn pure_shear_stress_state() {
119 let t = [[0.0, 50.0, 0.0], [50.0, 0.0, 0.0], [0.0, 0.0, 0.0]];
121 let s = cauchy_stress_analysis(&t);
122 assert!(
123 (s.von_mises - 3f64.sqrt() * 50.0).abs() < 1e-6,
124 "vm {}",
125 s.von_mises
126 );
127 assert!(
128 (s.principal[0] - 50.0).abs() < 1e-6,
129 "σ1 {}",
130 s.principal[0]
131 );
132 assert!(
133 (s.principal[2] + 50.0).abs() < 1e-6,
134 "σ3 {}",
135 s.principal[2]
136 );
137 assert!((s.max_shear - 50.0).abs() < 1e-6);
138 }
139
140 #[test]
141 fn drag_and_reynolds() {
142 assert!((drag_force(1.225, 10.0, 1.0, 2.0) - 122.5).abs() < 1e-6);
144 let re = reynolds_number(1.225, 10.0, 1.0, 1.8e-5);
146 assert!(re > 6.0e5 && re < 7.0e5, "Re {re}");
147 }
148
149 #[test]
150 fn fatigue_life_and_cumulative_damage() {
151 let n_low = fatigue_cycles_basquin(100.0, 900.0, -0.085);
153 let n_high = fatigue_cycles_basquin(300.0, 900.0, -0.085);
154 assert!(n_low > n_high, "lower stress should give longer life");
155 let d = miner_cumulative_damage(&[(500.0, 1000.0), (250.0, 500.0)]);
157 assert!((d - 1.0).abs() < 1e-9, "D {d}");
158 assert!(
159 miner_cumulative_damage(&[(100.0, 1000.0)]) < 1.0,
160 "safe block < 1"
161 );
162 }
163}