Skip to main content

oxiphysics_materials/aerospace/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5#[allow(unused_imports)]
6use super::functions::*;
7use std::f64::consts::PI;
8
9/// Energy-absorbing crash structure model (foam/tube progressive crushing).
10pub struct CrashAbsorber {
11    /// Designation.
12    pub designation: String,
13    /// Crush stress σ_c (MPa).
14    pub crush_stress: f64,
15    /// Trigger mechanism reduction factor (0.6–0.9).
16    pub trigger_factor: f64,
17    /// Cross-sectional area A (mm²).
18    pub area: f64,
19    /// Total crushable length L (mm).
20    pub length: f64,
21    /// Densification strain ε_d (typically 0.6–0.8).
22    pub densification_strain: f64,
23    /// Density of absorber material (kg/m³).
24    pub density: f64,
25}
26impl CrashAbsorber {
27    /// Create an aluminum foam crash absorber.
28    pub fn aluminum_foam(crush_stress: f64, area: f64, length: f64) -> Self {
29        CrashAbsorber {
30            designation: "Aluminum foam".to_string(),
31            crush_stress,
32            trigger_factor: 0.8,
33            area,
34            length,
35            densification_strain: 0.7,
36            density: 400.0,
37        }
38    }
39    /// Initial peak force with trigger mechanism (N).
40    pub fn peak_force(&self) -> f64 {
41        self.crush_stress * self.area * self.trigger_factor
42    }
43    /// Mean crush force (N).
44    pub fn mean_crush_force(&self) -> f64 {
45        self.crush_stress * self.area
46    }
47    /// Total energy absorbed (J) before densification.
48    pub fn total_energy_absorbed(&self) -> f64 {
49        let crush_length = self.length * self.densification_strain;
50        self.mean_crush_force() * crush_length
51    }
52    /// Specific energy absorption SEA (J/kg).
53    pub fn specific_energy_absorption(&self) -> f64 {
54        let mass = self.area * self.length * 1e-9 * self.density;
55        self.total_energy_absorbed() / mass.max(1e-30)
56    }
57    /// Crush stroke to absorb energy `e` (J).
58    pub fn stroke_for_energy(&self, e: f64) -> f64 {
59        e / self.mean_crush_force()
60    }
61}
62/// Nickel-base superalloy material properties (e.g., IN718, Rene 104).
63pub struct SuperalloyMaterial {
64    /// Alloy designation.
65    pub designation: String,
66    /// 0.2% proof strength at 650°C (MPa).
67    pub sigma_02_650: f64,
68    /// Ultimate tensile strength at 650°C (MPa).
69    pub uts_650: f64,
70    /// Creep rupture life at 150 MPa, 750°C (hours).
71    pub creep_rupture_life: f64,
72    /// Larson-Miller constant C.
73    pub larson_miller_c: f64,
74    /// Volume fraction of γ' precipitates.
75    pub gamma_prime_fraction: f64,
76    /// Oxidation rate constant kp at 900°C (mg²/cm⁴/h).
77    pub oxidation_kp: f64,
78    /// Density (kg/m³).
79    pub density: f64,
80}
81impl SuperalloyMaterial {
82    /// Inconel 718 standard properties.
83    pub fn in718() -> Self {
84        SuperalloyMaterial {
85            designation: "IN718".to_string(),
86            sigma_02_650: 1000.0,
87            uts_650: 1250.0,
88            creep_rupture_life: 1000.0,
89            larson_miller_c: 20.0,
90            gamma_prime_fraction: 0.18,
91            oxidation_kp: 1e-4,
92            density: 8190.0,
93        }
94    }
95    /// Larson-Miller parameter P = T * (C + log10(tr)) where T in Kelvin.
96    pub fn larson_miller_parameter(&self, temp_k: f64, rupture_hours: f64) -> f64 {
97        temp_k * (self.larson_miller_c + rupture_hours.log10())
98    }
99    /// Estimated creep rupture life at temperature and stress (simplified).
100    pub fn creep_rupture_estimate(&self, temp_k: f64, stress_mpa: f64) -> f64 {
101        let p_ref = self.larson_miller_parameter(923.15, self.creep_rupture_life);
102        let stress_factor = (self.sigma_02_650 / stress_mpa).powi(2);
103        let tr = 10.0_f64.powf(p_ref / temp_k - self.larson_miller_c);
104        tr * stress_factor
105    }
106    /// Oxidation mass gain after time `t` (hours): Δm = sqrt(kp * t).
107    pub fn oxidation_mass_gain(&self, t_hours: f64) -> f64 {
108        (self.oxidation_kp * t_hours).sqrt()
109    }
110}
111/// Ceramic matrix composite (SiC/SiC or C/SiC) material properties.
112pub struct CmcMaterial {
113    /// Material designation (e.g., "SiC/SiC").
114    pub designation: String,
115    /// Matrix-cracking stress σ_mc (MPa).
116    pub matrix_cracking_stress: f64,
117    /// Ultimate tensile strength UTS (MPa).
118    pub uts: f64,
119    /// Elastic modulus E (GPa).
120    pub modulus: f64,
121    /// Thermal conductivity k (W/m·K).
122    pub thermal_conductivity: f64,
123    /// Coefficient of thermal expansion CTE (1/K).
124    pub cte: f64,
125    /// Maximum use temperature (°C).
126    pub max_temperature: f64,
127    /// Fracture toughness KIc (MPa·√m).
128    pub k_ic: f64,
129}
130impl CmcMaterial {
131    /// SiC/SiC composite properties at room temperature.
132    pub fn sic_sic() -> Self {
133        CmcMaterial {
134            designation: "SiC/SiC".to_string(),
135            matrix_cracking_stress: 120.0,
136            uts: 400.0,
137            modulus: 230.0,
138            thermal_conductivity: 15.0,
139            cte: 4.0e-6,
140            max_temperature: 1300.0,
141            k_ic: 25.0,
142        }
143    }
144    /// C/SiC composite properties at room temperature.
145    pub fn c_sic() -> Self {
146        CmcMaterial {
147            designation: "C/SiC".to_string(),
148            matrix_cracking_stress: 80.0,
149            uts: 350.0,
150            modulus: 85.0,
151            thermal_conductivity: 10.0,
152            cte: 1.5e-6,
153            max_temperature: 1650.0,
154            k_ic: 20.0,
155        }
156    }
157    /// Critical crack length for brittle fracture: ac = (KIc / (Y * σ))² / π.
158    pub fn critical_crack_length(&self, sigma: f64, y: f64) -> f64 {
159        (self.k_ic / (y * sigma)).powi(2) / PI
160    }
161    /// Thermal shock resistance parameter: R = σ * (1 - ν) / (E * CTE).
162    pub fn thermal_shock_resistance(&self, nu: f64) -> f64 {
163        self.uts * (1.0 - nu) / (self.modulus * 1e3 * self.cte)
164    }
165}
166/// Honeycomb sandwich panel structural properties.
167pub struct HoneycombSandwich {
168    /// Core cell size (mm).
169    pub cell_size: f64,
170    /// Core density (kg/m³).
171    pub core_density: f64,
172    /// Ribbon direction shear modulus GRT (MPa).
173    pub g_rt: f64,
174    /// Transverse direction shear modulus GLT (MPa).
175    pub g_lt: f64,
176    /// Face sheet thickness tf (mm).
177    pub face_thickness: f64,
178    /// Core thickness tc (mm).
179    pub core_thickness: f64,
180    /// Face sheet modulus Ef (GPa).
181    pub face_modulus: f64,
182}
183impl HoneycombSandwich {
184    /// Create an aluminum honeycomb sandwich panel.
185    pub fn aluminum_honeycomb(
186        cell_size: f64,
187        core_density: f64,
188        face_thickness: f64,
189        core_thickness: f64,
190    ) -> Self {
191        let g_rt = 100.0 * (core_density / 100.0).powf(1.5);
192        let g_lt = 50.0 * (core_density / 100.0).powf(1.5);
193        HoneycombSandwich {
194            cell_size,
195            core_density,
196            g_rt,
197            g_lt,
198            face_thickness,
199            core_thickness,
200            face_modulus: 70.0,
201        }
202    }
203    /// Total panel thickness h (mm).
204    pub fn total_thickness(&self) -> f64 {
205        2.0 * self.face_thickness + self.core_thickness
206    }
207    /// Equivalent bending stiffness D (N·mm) per unit width.
208    /// D ≈ Ef * tf * d² / 2 where d = distance between face centroids.
209    pub fn flexural_rigidity(&self) -> f64 {
210        let d = self.core_thickness + self.face_thickness;
211        self.face_modulus * 1000.0 * self.face_thickness * d * d / 2.0
212    }
213    /// Face sheet wrinkling stress σ_w (MPa).
214    pub fn wrinkling_stress(&self) -> f64 {
215        let ec = 2.0 * self.g_lt;
216        0.5 * (self.face_modulus * 1000.0 * ec * self.g_rt).powf(1.0 / 3.0)
217    }
218    /// Core shear stress limit τ_max (MPa) for panel of width b under load P.
219    pub fn shear_stress(&self, shear_force: f64) -> f64 {
220        let h = self.total_thickness();
221        shear_force / (h - 2.0 * self.face_thickness)
222    }
223}
224/// Extended titanium alloy model with creep and high-temperature behavior.
225pub struct TitaniumAlloyExtended {
226    /// Base designation.
227    pub designation: String,
228    /// Room-temperature yield strength (MPa).
229    pub sigma_y_rt: f64,
230    /// Elevated-temperature yield strength at 300°C (MPa).
231    pub sigma_y_300: f64,
232    /// Creep activation energy Q (J/mol).
233    pub creep_q: f64,
234    /// Creep pre-exponential constant A.
235    pub creep_a: f64,
236    /// Creep stress exponent n.
237    pub creep_n: f64,
238    /// Oxidation rate constant at 600°C kp (mg²/cm⁴/h).
239    pub kp_600: f64,
240    /// Young's modulus at RT (GPa).
241    pub e_rt: f64,
242    /// Young's modulus temperature coefficient (GPa/°C).
243    pub de_dt: f64,
244    /// Density (kg/m³).
245    pub density: f64,
246}
247impl TitaniumAlloyExtended {
248    /// Ti-6Al-4V elevated temperature properties.
249    pub fn ti64_elevated() -> Self {
250        TitaniumAlloyExtended {
251            designation: "Ti-6Al-4V (elevated)".to_string(),
252            sigma_y_rt: 880.0,
253            sigma_y_300: 700.0,
254            creep_q: 250_000.0,
255            creep_a: 1.0e8,
256            creep_n: 4.0,
257            kp_600: 2.0e-3,
258            e_rt: 114.0,
259            de_dt: -0.05,
260            density: 4430.0,
261        }
262    }
263    /// Ti-6Al-2Sn-4Zr-6Mo high-temperature alloy properties.
264    pub fn ti6246() -> Self {
265        TitaniumAlloyExtended {
266            designation: "Ti-6-2-4-6".to_string(),
267            sigma_y_rt: 1100.0,
268            sigma_y_300: 900.0,
269            creep_q: 270_000.0,
270            creep_a: 5.0e7,
271            creep_n: 3.5,
272            kp_600: 1.0e-3,
273            e_rt: 120.0,
274            de_dt: -0.055,
275            density: 4540.0,
276        }
277    }
278    /// Young's modulus at temperature T (°C).
279    pub fn youngs_modulus_at(&self, t_celsius: f64) -> f64 {
280        (self.e_rt + self.de_dt * t_celsius).max(50.0)
281    }
282    /// Interpolated yield strength at temperature T (°C), linear between RT and 300°C.
283    pub fn yield_strength_at(&self, t_celsius: f64) -> f64 {
284        let t = t_celsius.clamp(20.0, 300.0);
285        self.sigma_y_rt + (self.sigma_y_300 - self.sigma_y_rt) * (t - 20.0) / 280.0
286    }
287    /// Steady-state creep rate ε̇ = A * σ^n * exp(-Q/RT) (s⁻¹).
288    pub fn creep_rate(&self, sigma_mpa: f64, t_kelvin: f64) -> f64 {
289        const R: f64 = 8.314;
290        self.creep_a * sigma_mpa.powf(self.creep_n) * (-self.creep_q / (R * t_kelvin)).exp()
291    }
292    /// Time to 0.2% creep strain at constant stress and temperature.
293    pub fn time_to_02pct_creep(&self, sigma_mpa: f64, t_kelvin: f64) -> f64 {
294        let eps_dot = self.creep_rate(sigma_mpa, t_kelvin);
295        if eps_dot < 1e-30 {
296            return f64::INFINITY;
297        }
298        0.002 / eps_dot
299    }
300    /// Oxidation mass gain (mg/cm²) after `t` hours at 600°C (parabolic law).
301    pub fn oxidation_gain_600(&self, t_hours: f64) -> f64 {
302        (self.kp_600 * t_hours).sqrt()
303    }
304}
305/// CFRP laminate using Classical Lamination Theory (CLT).
306pub struct CfrpLaminate {
307    /// Plies from bottom to top.
308    pub plies: Vec<CfrpPly>,
309    /// Total thickness (mm).
310    pub thickness: f64,
311}
312impl CfrpLaminate {
313    /// Create a laminate from a list of plies.
314    pub fn new(plies: Vec<CfrpPly>) -> Self {
315        let thickness = plies.iter().map(|p| p.thickness).sum();
316        CfrpLaminate { plies, thickness }
317    }
318    /// Create a symmetric quasi-isotropic layup \[0/45/-45/90\]s with given ply thickness.
319    pub fn quasi_isotropic(ply_thickness: f64) -> Self {
320        let angles = [0.0, 45.0, -45.0, 90.0, 90.0, -45.0, 45.0, 0.0];
321        let plies = angles
322            .iter()
323            .map(|&a| CfrpPly::im7_8552(a, ply_thickness))
324            .collect();
325        Self::new(plies)
326    }
327    /// Compute the A-matrix (in-plane stiffness, N/mm) as \[A11, A22, A12, A66\].
328    pub fn a_matrix(&self) -> [f64; 4] {
329        let mut a11 = 0.0f64;
330        let mut a22 = 0.0f64;
331        let mut a12 = 0.0f64;
332        let mut a66 = 0.0f64;
333        for ply in &self.plies {
334            let q = ply.transformed_stiffness();
335            let t = ply.thickness;
336            a11 += q[0] * t;
337            a22 += q[1] * t;
338            a12 += q[2] * t;
339            a66 += q[3] * t;
340        }
341        [a11, a22, a12, a66]
342    }
343    /// Effective in-plane longitudinal stiffness Ex (GPa).
344    pub fn effective_ex(&self) -> f64 {
345        let [a11, _a22, a12, _a66] = self.a_matrix();
346        (a11 - a12 * a12 / a11) / self.thickness
347    }
348    /// First ply failure index using Tsai-Hill criterion for each ply.
349    /// Returns the minimum reserve factor (>1 = no failure).
350    pub fn first_ply_failure_index(&self, nx: f64, ny: f64, nxy: f64) -> f64 {
351        self.plies
352            .iter()
353            .map(|ply| {
354                let q = ply.transformed_stiffness();
355                let sigma1 = q[0] * nx / ply.thickness;
356                let sigma2 = q[1] * ny / ply.thickness;
357                let tau12 = q[3] * nxy / ply.thickness;
358                let fi = (sigma1 / ply.f1t).powi(2) - (sigma1 * sigma2) / (ply.f1t * ply.f1t)
359                    + (sigma2 / ply.f2t).powi(2)
360                    + (tau12 / ply.f6).powi(2);
361                if fi > 0.0 {
362                    1.0 / fi.sqrt()
363                } else {
364                    f64::INFINITY
365                }
366            })
367            .fold(f64::INFINITY, f64::min)
368    }
369}
370/// Aerogel thermal insulation: Knudsen effect, effective conductivity.
371pub struct AerogelInsulation {
372    /// Solid skeleton thermal conductivity (W/m·K).
373    pub k_solid: f64,
374    /// Radiative conductivity at 25°C (W/m·K).
375    pub k_rad_25: f64,
376    /// Porosity (0–1).
377    pub porosity: f64,
378    /// Mean pore diameter (nm).
379    pub pore_diameter_nm: f64,
380    /// Density (kg/m³).
381    pub density: f64,
382    /// Operating temperature (°C).
383    pub temperature: f64,
384    /// Gas (air) thermal conductivity at STP (W/m·K).
385    pub k_gas_stp: f64,
386}
387impl AerogelInsulation {
388    /// Silica aerogel blanket at 25°C, ambient pressure.
389    pub fn silica_aerogel() -> Self {
390        AerogelInsulation {
391            k_solid: 0.012,
392            k_rad_25: 0.003,
393            porosity: 0.95,
394            pore_diameter_nm: 20.0,
395            density: 120.0,
396            temperature: 25.0,
397            k_gas_stp: 0.0257,
398        }
399    }
400    /// Pyrogel XT (Aspen) high-temperature aerogel blanket.
401    pub fn pyrogel_xt() -> Self {
402        AerogelInsulation {
403            k_solid: 0.015,
404            k_rad_25: 0.005,
405            porosity: 0.92,
406            pore_diameter_nm: 15.0,
407            density: 170.0,
408            temperature: 25.0,
409            k_gas_stp: 0.0257,
410        }
411    }
412    /// Knudsen number Kn = λ_mfp / pore_diameter.
413    /// λ_mfp (air at STP) ≈ 68 nm.
414    pub fn knudsen_number(&self, pressure_pa: f64) -> f64 {
415        let lambda_stp = 68.0e-9;
416        let lambda = lambda_stp * 101_325.0 / pressure_pa;
417        lambda / (self.pore_diameter_nm * 1.0e-9)
418    }
419    /// Gas conductivity reduced by Knudsen effect.
420    pub fn gas_conductivity_knudsen(&self, pressure_pa: f64) -> f64 {
421        let kn = self.knudsen_number(pressure_pa);
422        self.k_gas_stp / (1.0 + 2.0 * kn)
423    }
424    /// Effective thermal conductivity at pressure and temperature.
425    pub fn effective_conductivity(&self, pressure_pa: f64) -> f64 {
426        let k_gas = self.gas_conductivity_knudsen(pressure_pa);
427        let t_factor = ((self.temperature + 273.15) / 298.15).powi(3);
428        let k_rad = self.k_rad_25 * t_factor;
429        (1.0 - self.porosity) * self.k_solid + self.porosity * k_gas + k_rad
430    }
431    /// Vacuum thermal conductivity (≈ solid + radiative only).
432    pub fn vacuum_conductivity(&self) -> f64 {
433        let t_factor = ((self.temperature + 273.15) / 298.15).powi(3);
434        let k_rad = self.k_rad_25 * t_factor;
435        (1.0 - self.porosity) * self.k_solid + k_rad
436    }
437}
438/// Reusable thermal protection system (TPS) tile properties.
439pub struct ThermalProtection {
440    /// Material designation (PICA, TUFI, RCC, FRCI).
441    pub designation: String,
442    /// Thermal conductivity at room temperature (W/m·K).
443    pub k_room: f64,
444    /// Thermal conductivity at max temperature (W/m·K).
445    pub k_hot: f64,
446    /// Specific heat at room temperature (J/kg·K).
447    pub cp_room: f64,
448    /// Emissivity ε (dimensionless, 0–1).
449    pub emissivity: f64,
450    /// Max use temperature (°C).
451    pub max_temp: f64,
452    /// Density (kg/m³).
453    pub density: f64,
454}
455impl ThermalProtection {
456    /// PICA (Phenolic Impregnated Carbon Ablator) properties.
457    pub fn pica() -> Self {
458        ThermalProtection {
459            designation: "PICA".to_string(),
460            k_room: 0.18,
461            k_hot: 0.45,
462            cp_room: 1700.0,
463            emissivity: 0.9,
464            max_temp: 1650.0,
465            density: 280.0,
466        }
467    }
468    /// Interpolated thermal conductivity at temperature T (°C).
469    pub fn conductivity_at(&self, t: f64) -> f64 {
470        let t_frac = (t / self.max_temp).min(1.0);
471        self.k_room + (self.k_hot - self.k_room) * t_frac
472    }
473    /// Radiation heat rejection rate q_rad (W/m²) at surface temp T (K).
474    pub fn radiation_heat_flux(&self, t_k: f64) -> f64 {
475        const SIGMA: f64 = 5.67e-8;
476        self.emissivity * SIGMA * t_k.powi(4)
477    }
478    /// Required thickness to limit back-face temperature (simplified 1D conduction).
479    pub fn required_thickness(&self, q_in: f64, dt_max: f64) -> f64 {
480        let k_avg = (self.k_room + self.k_hot) / 2.0;
481        k_avg * dt_max / q_in
482    }
483}
484/// Carbon-carbon (C/C) composite with oxidation protection model.
485pub struct CarbonCarbonComposite {
486    /// Fiber volume fraction.
487    pub vf: f64,
488    /// Axial (fiber direction) thermal conductivity (W/m·K).
489    pub k_axial: f64,
490    /// Transverse thermal conductivity (W/m·K).
491    pub k_transverse: f64,
492    /// Room-temperature tensile strength (MPa).
493    pub strength_rt: f64,
494    /// Tensile strength at 2000°C in inert atmosphere (MPa).
495    pub strength_2000: f64,
496    /// SiC coating thickness (μm).
497    pub sic_coat_thickness: f64,
498    /// Oxidation onset temperature in air (°C).
499    pub oxidation_onset_temp: f64,
500    /// Oxidation rate constant at 600°C (mg/cm²/h).
501    pub kox_600: f64,
502    /// Density (kg/m³).
503    pub density: f64,
504}
505impl CarbonCarbonComposite {
506    /// 2D woven C/C composite with SiC-SiC coating.
507    pub fn cc_2d_woven() -> Self {
508        CarbonCarbonComposite {
509            vf: 0.50,
510            k_axial: 150.0,
511            k_transverse: 30.0,
512            strength_rt: 250.0,
513            strength_2000: 350.0,
514            sic_coat_thickness: 200.0,
515            oxidation_onset_temp: 450.0,
516            kox_600: 0.5,
517            density: 1800.0,
518        }
519    }
520    /// 3D C/C composite for rocket nozzle throats.
521    pub fn cc_3d_nozzle() -> Self {
522        CarbonCarbonComposite {
523            vf: 0.55,
524            k_axial: 120.0,
525            k_transverse: 50.0,
526            strength_rt: 300.0,
527            strength_2000: 400.0,
528            sic_coat_thickness: 300.0,
529            oxidation_onset_temp: 400.0,
530            kox_600: 0.3,
531            density: 1950.0,
532        }
533    }
534    /// Effective through-thickness thermal conductivity (parallel model).
535    pub fn effective_conductivity(&self) -> f64 {
536        self.vf * self.k_axial + (1.0 - self.vf) * self.k_transverse
537    }
538    /// Oxidation recession (μm) after `t` hours at `temp` °C.
539    /// Uses simplified Arrhenius scaling from 600°C reference.
540    pub fn oxidation_recession_um(&self, temp_celsius: f64, t_hours: f64) -> f64 {
541        if temp_celsius < self.oxidation_onset_temp {
542            return 0.0;
543        }
544        const Q_OX: f64 = 120_000.0;
545        const R_GAS: f64 = 8.314;
546        let t_ref = 873.15;
547        let t_k = temp_celsius + 273.15;
548        let k_scaled = self.kox_600 * ((-Q_OX / R_GAS) * (1.0 / t_k - 1.0 / t_ref)).exp();
549        k_scaled * t_hours
550    }
551    /// SiC coating protection effectiveness: fraction of surface protected.
552    pub fn coating_protection_factor(&self) -> f64 {
553        1.0 - (-self.sic_coat_thickness / 100.0).exp()
554    }
555    /// Strength retention ratio at temperature T (°C) in inert atmosphere.
556    pub fn strength_retention(&self, t_celsius: f64) -> f64 {
557        let ratio = (t_celsius / 2000.0).clamp(0.0, 1.0);
558        let str_t = self.strength_rt * (1.0 - ratio) + self.strength_2000 * ratio;
559        str_t / self.strength_rt
560    }
561}
562/// Foam-core sandwich panel: shear, bending, and indentation failure analysis.
563pub struct FoamCoreSandwich {
564    /// Core material designation.
565    pub core_designation: String,
566    /// Core density (kg/m³).
567    pub core_density: f64,
568    /// Core Young's modulus Ec (MPa).
569    pub ec: f64,
570    /// Core shear modulus Gc (MPa).
571    pub gc: f64,
572    /// Core compressive strength σc (MPa).
573    pub sigma_c: f64,
574    /// Core shear strength τc (MPa).
575    pub tau_c: f64,
576    /// Face sheet thickness tf (mm).
577    pub tf: f64,
578    /// Face sheet Young's modulus Ef (GPa).
579    pub ef: f64,
580    /// Face sheet tensile strength σf (MPa).
581    pub sigma_f: f64,
582    /// Core thickness tc (mm).
583    pub tc: f64,
584}
585impl FoamCoreSandwich {
586    /// Rohacell 51 WF PMI foam core sandwich with CFRP faces.
587    pub fn rohacell51_cfrp(tf: f64, tc: f64) -> Self {
588        FoamCoreSandwich {
589            core_designation: "Rohacell 51 WF".to_string(),
590            core_density: 52.0,
591            ec: 70.0,
592            gc: 19.0,
593            sigma_c: 0.9,
594            tau_c: 1.3,
595            tf,
596            ef: 70.0,
597            sigma_f: 600.0,
598            tc,
599        }
600    }
601    /// Airex C70 PVC foam with glass/epoxy faces.
602    pub fn airex_c70_gfrp(tf: f64, tc: f64) -> Self {
603        FoamCoreSandwich {
604            core_designation: "Airex C70".to_string(),
605            core_density: 75.0,
606            ec: 100.0,
607            gc: 35.0,
608            sigma_c: 1.4,
609            tau_c: 1.7,
610            tf,
611            ef: 20.0,
612            sigma_f: 300.0,
613            tc,
614        }
615    }
616    /// Total panel thickness (mm).
617    pub fn total_thickness(&self) -> f64 {
618        2.0 * self.tf + self.tc
619    }
620    /// Bending stiffness D (N·mm) per unit width (Euler-Bernoulli, face-dominated).
621    pub fn bending_stiffness(&self) -> f64 {
622        let d = self.tc + self.tf;
623        self.ef * 1000.0 * self.tf * d * d / 2.0
624    }
625    /// Core shear stiffness S = Gc * tc (N/mm per unit width).
626    pub fn shear_stiffness(&self) -> f64 {
627        self.gc * self.tc
628    }
629    /// Maximum allowable distributed load (N/mm) limited by core shear failure.
630    pub fn max_load_shear(&self, span_mm: f64) -> f64 {
631        2.0 * self.tau_c * self.total_thickness() / (3.0 * span_mm)
632    }
633    /// Indentation failure load (N) using elastic foundation model.
634    pub fn indentation_load(&self, indent_radius_mm: f64) -> f64 {
635        PI * indent_radius_mm * (self.ec * self.sigma_f).sqrt() * self.tf
636    }
637    /// Face sheet wrinkling stress (MPa) using Euler buckling of face over core.
638    pub fn wrinkling_stress_foam(&self) -> f64 {
639        0.5 * (self.ef * 1000.0 * self.ec * self.gc).powf(1.0 / 3.0)
640    }
641}
642/// Nickel-base superalloy: single crystal, directional solidification, and oxidation.
643pub struct NickelSuperalloyExtended {
644    /// Alloy designation.
645    pub designation: String,
646    /// Volume fraction of γ' precipitate.
647    pub gamma_prime_vf: f64,
648    /// γ' lattice misfit δ (dimensionless).
649    pub misfit: f64,
650    /// Room-temperature tensile strength (MPa).
651    pub uts_rt: f64,
652    /// Tensile strength at 1000°C (MPa).
653    pub uts_1000: f64,
654    /// Creep rupture life at 137 MPa, 982°C (hours).
655    pub creep_life_ref: f64,
656    /// Larson-Miller C constant.
657    pub lm_c: f64,
658    /// Oxidation parabolic constant at 1000°C (mg²/cm⁴/h).
659    pub kp_1000: f64,
660    /// MCrAlY bond coat thickness (μm).
661    pub bond_coat_thickness: f64,
662    /// Density (kg/m³).
663    pub density: f64,
664}
665impl NickelSuperalloyExtended {
666    /// CMSX-4 single-crystal superalloy properties.
667    pub fn cmsx4() -> Self {
668        NickelSuperalloyExtended {
669            designation: "CMSX-4".to_string(),
670            gamma_prime_vf: 0.70,
671            misfit: -0.002,
672            uts_rt: 1400.0,
673            uts_1000: 950.0,
674            creep_life_ref: 1000.0,
675            lm_c: 21.5,
676            kp_1000: 5.0e-5,
677            bond_coat_thickness: 100.0,
678            density: 8700.0,
679        }
680    }
681    /// Rene 142 directionally-solidified superalloy.
682    pub fn rene142() -> Self {
683        NickelSuperalloyExtended {
684            designation: "Rene 142".to_string(),
685            gamma_prime_vf: 0.65,
686            misfit: -0.0015,
687            uts_rt: 1350.0,
688            uts_1000: 880.0,
689            creep_life_ref: 800.0,
690            lm_c: 21.0,
691            kp_1000: 6.0e-5,
692            bond_coat_thickness: 80.0,
693            density: 8820.0,
694        }
695    }
696    /// Larson-Miller creep parameter P = T*(C + log10(tr)).
697    pub fn larson_miller_parameter(&self, t_kelvin: f64, tr_hours: f64) -> f64 {
698        t_kelvin * (self.lm_c + tr_hours.log10())
699    }
700    /// Estimated creep rupture life at (T, σ) relative to reference condition.
701    pub fn estimated_rupture_life(&self, t_kelvin: f64, sigma_mpa: f64) -> f64 {
702        let p_ref = self.larson_miller_parameter(1255.15, self.creep_life_ref);
703        let stress_ratio = self.uts_rt / sigma_mpa;
704        let tr = 10.0_f64.powf(p_ref / t_kelvin - self.lm_c);
705        tr * stress_ratio.powi(2)
706    }
707    /// Oxidation mass gain at 1000°C after `t` hours (parabolic law).
708    pub fn oxidation_gain(&self, t_hours: f64) -> f64 {
709        (self.kp_1000 * t_hours).sqrt()
710    }
711    /// γ' strengthening increment (MPa) from Orowan bypassing.
712    /// Δσ = M * G * b / (λ - 2r) where λ is particle spacing.
713    pub fn gamma_prime_strengthening(&self, r_nm: f64, g_gpa: f64) -> f64 {
714        const M: f64 = 3.06;
715        const B: f64 = 0.254e-9;
716        let r = r_nm * 1.0e-9;
717        let lambda = r * (2.0 * PI / (3.0 * self.gamma_prime_vf)).sqrt();
718        if lambda - 2.0 * r < 1.0e-12 {
719            return 0.0;
720        }
721        M * g_gpa * 1.0e9 * B / (lambda - 2.0 * r) / 1.0e6
722    }
723    /// Thermal barrier coating effectiveness: fraction of temperature drop across TBC.
724    pub fn tbc_temperature_fraction(&self, k_tbc: f64, k_substrate: f64, tbc_thick_um: f64) -> f64 {
725        let tbc_m = tbc_thick_um * 1.0e-6;
726        let sub_m = self.bond_coat_thickness * 1.0e-6;
727        let r_tbc = tbc_m / k_tbc;
728        let r_sub = sub_m / k_substrate;
729        r_tbc / (r_tbc + r_sub)
730    }
731}
732/// Ablative heat shield material model (Fay-Riddell recession rate model).
733pub struct AblativeHeatShield {
734    /// Material designation (e.g., "AVCOAT", "SLA-561V").
735    pub designation: String,
736    /// Effective heat of ablation H_eff (MJ/kg).
737    pub h_eff: f64,
738    /// Char layer thermal conductivity kc (W/m·K).
739    pub k_char: f64,
740    /// Virgin material thermal conductivity kv (W/m·K).
741    pub k_virgin: f64,
742    /// Char density ρc (kg/m³).
743    pub rho_char: f64,
744    /// Virgin density ρv (kg/m³).
745    pub rho_virgin: f64,
746    /// Char specific heat cp_c (J/kg·K).
747    pub cp_char: f64,
748    /// Pyrolysis decomposition temperature (°C).
749    pub pyrolysis_temp: f64,
750}
751impl AblativeHeatShield {
752    /// AVCOAT 5026-39G/HC ablator properties (Apollo era).
753    pub fn avcoat() -> Self {
754        AblativeHeatShield {
755            designation: "AVCOAT 5026-39G/HC".to_string(),
756            h_eff: 32.0,
757            k_char: 0.5,
758            k_virgin: 0.3,
759            rho_char: 240.0,
760            rho_virgin: 520.0,
761            cp_char: 1250.0,
762            pyrolysis_temp: 350.0,
763        }
764    }
765    /// Recession rate ṙ (m/s) given surface heat flux q_w (W/m²).
766    /// ṙ = q_w / (ρv * H_eff * 1e6).
767    pub fn recession_rate(&self, q_w: f64) -> f64 {
768        q_w / (self.rho_virgin * self.h_eff * 1e6)
769    }
770    /// Char layer thickness after time `t` (s) at constant heat flux.
771    pub fn char_thickness(&self, q_w: f64, t: f64) -> f64 {
772        self.recession_rate(q_w) * t
773    }
774    /// Mass loss per unit area (kg/m²) for given heat flux and time.
775    pub fn mass_loss(&self, q_w: f64, t: f64) -> f64 {
776        self.rho_virgin * self.char_thickness(q_w, t)
777    }
778}
779/// Ti-6Al-4V titanium alloy material model.
780pub struct TitaniumAlloy {
781    /// Designation.
782    pub designation: String,
783    /// Young's modulus E (GPa).
784    pub e: f64,
785    /// 0.2% yield strength (MPa).
786    pub sigma_y: f64,
787    /// Ultimate tensile strength UTS (MPa).
788    pub uts: f64,
789    /// Fatigue threshold ΔK_th (MPa·√m).
790    pub delta_k_th: f64,
791    /// Paris law coefficient C (m/cycle per (MPa·√m)^m).
792    pub paris_c: f64,
793    /// Paris law exponent m.
794    pub paris_m: f64,
795    /// Density (kg/m³).
796    pub density: f64,
797    /// α-phase volume fraction.
798    pub alpha_fraction: f64,
799}
800impl TitaniumAlloy {
801    /// Ti-6Al-4V mill-annealed standard properties.
802    pub fn ti64() -> Self {
803        TitaniumAlloy {
804            designation: "Ti-6Al-4V".to_string(),
805            e: 114.0,
806            sigma_y: 880.0,
807            uts: 950.0,
808            delta_k_th: 3.0,
809            paris_c: 1.0e-11,
810            paris_m: 3.2,
811            density: 4430.0,
812            alpha_fraction: 0.85,
813        }
814    }
815    /// Paris-Erdogan fatigue crack growth rate da/dN (m/cycle).
816    pub fn crack_growth_rate(&self, delta_k: f64) -> f64 {
817        if delta_k < self.delta_k_th {
818            return 0.0;
819        }
820        self.paris_c * delta_k.powf(self.paris_m)
821    }
822    /// Number of cycles to grow crack from `a0` to `af` (m) under ΔK = Δσ * sqrt(π * a).
823    pub fn fatigue_life_cycles(&self, a0: f64, af: f64, delta_sigma: f64) -> f64 {
824        let steps = 1000usize;
825        let da = (af - a0) / steps as f64;
826        let mut n = 0.0f64;
827        for i in 0..steps {
828            let a = a0 + (i as f64 + 0.5) * da;
829            let dk = delta_sigma * (PI * a).sqrt();
830            let dadn = self.crack_growth_rate(dk);
831            if dadn > 0.0 {
832                n += da / dadn;
833            }
834        }
835        n
836    }
837    /// SCC threshold KISCC ≈ 0.5 * KIc. Returns critical stress for SCC initiation.
838    pub fn scc_critical_stress(&self, a: f64, k_ic: f64) -> f64 {
839        let k_scc = 0.5 * k_ic;
840        k_scc / (PI * a).sqrt()
841    }
842}
843/// Detailed ablation char/virgin material model tracking recession front.
844pub struct AblationCharModel {
845    /// Virgin material density (kg/m³).
846    pub rho_virgin: f64,
847    /// Char density (kg/m³).
848    pub rho_char: f64,
849    /// Effective heat of ablation (MJ/kg).
850    pub h_eff: f64,
851    /// Char thermal conductivity (W/m·K).
852    pub k_char: f64,
853    /// Virgin thermal conductivity (W/m·K).
854    pub k_virgin: f64,
855    /// Virgin specific heat (J/kg·K).
856    pub cp_virgin: f64,
857    /// Char specific heat (J/kg·K).
858    pub cp_char: f64,
859    /// Pyrolysis start temperature (°C).
860    pub t_pyrolysis_start: f64,
861    /// Pyrolysis completion temperature (°C).
862    pub t_pyrolysis_end: f64,
863    /// Pyrolysis enthalpy (J/kg).
864    pub h_pyrolysis: f64,
865}
866impl AblationCharModel {
867    /// Phenolic impregnated carbon ablator (PICA) char model.
868    pub fn pica_char() -> Self {
869        AblationCharModel {
870            rho_virgin: 270.0,
871            rho_char: 180.0,
872            h_eff: 28.0,
873            k_char: 0.45,
874            k_virgin: 0.18,
875            cp_virgin: 1700.0,
876            cp_char: 1250.0,
877            t_pyrolysis_start: 400.0,
878            t_pyrolysis_end: 700.0,
879            h_pyrolysis: 2.3e6,
880        }
881    }
882    /// Carbon phenolic ablator for ballistic re-entry.
883    pub fn carbon_phenolic() -> Self {
884        AblationCharModel {
885            rho_virgin: 1450.0,
886            rho_char: 1250.0,
887            h_eff: 42.0,
888            k_char: 2.5,
889            k_virgin: 1.2,
890            cp_virgin: 1500.0,
891            cp_char: 1800.0,
892            t_pyrolysis_start: 350.0,
893            t_pyrolysis_end: 600.0,
894            h_pyrolysis: 3.5e6,
895        }
896    }
897    /// Charring fraction: fraction converted to char given surface temperature.
898    pub fn char_fraction(&self, t_surface: f64) -> f64 {
899        if t_surface <= self.t_pyrolysis_start {
900            return 0.0;
901        }
902        if t_surface >= self.t_pyrolysis_end {
903            return 1.0;
904        }
905        (t_surface - self.t_pyrolysis_start) / (self.t_pyrolysis_end - self.t_pyrolysis_start)
906    }
907    /// Effective density at a given char fraction.
908    pub fn effective_density(&self, char_frac: f64) -> f64 {
909        let f = char_frac.clamp(0.0, 1.0);
910        self.rho_virgin * (1.0 - f) + self.rho_char * f
911    }
912    /// Effective thermal conductivity at a given char fraction.
913    pub fn effective_conductivity(&self, char_frac: f64) -> f64 {
914        let f = char_frac.clamp(0.0, 1.0);
915        self.k_virgin * (1.0 - f) + self.k_char * f
916    }
917    /// Surface recession velocity (m/s) at given heat flux q_w (W/m²).
918    pub fn recession_velocity(&self, q_w: f64) -> f64 {
919        q_w / (self.rho_virgin * self.h_eff * 1.0e6)
920    }
921    /// Total char thickness (m) after time `t` (s) at steady heat flux.
922    pub fn char_thickness_at_time(&self, q_w: f64, t: f64) -> f64 {
923        self.recession_velocity(q_w) * t
924    }
925    /// Pyrolysis gas mass flux (kg/m²/s): ṁg = (ρv - ρc) * ṡ.
926    pub fn pyrolysis_gas_flux(&self, q_w: f64) -> f64 {
927        let s_dot = self.recession_velocity(q_w);
928        (self.rho_virgin - self.rho_char) * s_dot
929    }
930    /// Backface temperature estimate using 1D steady-state conduction through char.
931    pub fn backface_temperature(&self, q_w: f64, char_thick: f64, t_front: f64) -> f64 {
932        if char_thick < 1e-12 {
933            return t_front;
934        }
935        t_front - q_w * char_thick / self.k_char
936    }
937}
938/// Full ABD matrix computation for composite laminate (CLT).
939pub struct CompositeLaminateClt {
940    /// Plies with angle (deg) and thickness (mm), E1/E2/G12 (GPa), nu12.
941    pub plies: Vec<(f64, f64, f64, f64, f64, f64)>,
942}
943impl CompositeLaminateClt {
944    /// Create a new CLT laminate from angle/thickness/moduli tuples.
945    /// Each tuple: `(angle_deg, thickness_mm, e1_gpa, e2_gpa, g12_gpa, nu12)`.
946    pub fn new(plies: Vec<(f64, f64, f64, f64, f64, f64)>) -> Self {
947        CompositeLaminateClt { plies }
948    }
949    /// Create a cross-ply \[0/90\]ns from ply count and thickness.
950    pub fn cross_ply(
951        n_pairs: usize,
952        ply_thickness_mm: f64,
953        e1: f64,
954        e2: f64,
955        g12: f64,
956        nu12: f64,
957    ) -> Self {
958        let mut plies = Vec::new();
959        for _ in 0..n_pairs {
960            plies.push((0.0, ply_thickness_mm, e1, e2, g12, nu12));
961            plies.push((90.0, ply_thickness_mm, e1, e2, g12, nu12));
962        }
963        Self::new(plies)
964    }
965    /// Total laminate thickness (mm).
966    pub fn total_thickness_mm(&self) -> f64 {
967        self.plies.iter().map(|p| p.1).sum()
968    }
969    /// Compute A11 component of ABD matrix (N/mm).
970    pub fn a11(&self) -> f64 {
971        self.plies
972            .iter()
973            .map(|p| {
974                let (angle, t, e1, e2, g12, nu12) = *p;
975                let nu21 = nu12 * e2 / e1;
976                let denom = 1.0 - nu12 * nu21;
977                let q11 = e1 / denom;
978                let q22 = e2 / denom;
979                let q12 = nu12 * e2 / denom;
980                let q66 = g12;
981                let c = angle.to_radians().cos();
982                let s = angle.to_radians().sin();
983                let c2 = c * c;
984                let s2 = s * s;
985                let c4 = c2 * c2;
986                let s4 = s2 * s2;
987                let q11b = q11 * c4 + 2.0 * (q12 + 2.0 * q66) * s2 * c2 + q22 * s4;
988                q11b * t
989            })
990            .sum()
991    }
992    /// Compute A22 component (N/mm).
993    pub fn a22(&self) -> f64 {
994        self.plies
995            .iter()
996            .map(|p| {
997                let (angle, t, e1, e2, g12, nu12) = *p;
998                let nu21 = nu12 * e2 / e1;
999                let denom = 1.0 - nu12 * nu21;
1000                let q11 = e1 / denom;
1001                let q22 = e2 / denom;
1002                let q12 = nu12 * e2 / denom;
1003                let q66 = g12;
1004                let c = angle.to_radians().cos();
1005                let s = angle.to_radians().sin();
1006                let c2 = c * c;
1007                let s2 = s * s;
1008                let c4 = c2 * c2;
1009                let s4 = s2 * s2;
1010                let q22b = q11 * s4 + 2.0 * (q12 + 2.0 * q66) * s2 * c2 + q22 * c4;
1011                q22b * t
1012            })
1013            .sum()
1014    }
1015    /// Interlaminar shear stress (MPa) estimate under transverse shear V (N/mm).
1016    /// τ_max ≈ 1.5 * V / h for rectangular cross-section.
1017    pub fn interlaminar_shear_stress(&self, v_per_width: f64) -> f64 {
1018        let h = self.total_thickness_mm();
1019        1.5 * v_per_width / h
1020    }
1021    /// Tsai-Wu failure index (simplified plane stress) for the critical ply.
1022    /// Returns max FI over all plies (FI > 1 = failure).
1023    #[allow(clippy::too_many_arguments)]
1024    pub fn tsai_wu_index(
1025        &self,
1026        nx: f64,
1027        ny: f64,
1028        nxy: f64,
1029        xt: f64,
1030        xc: f64,
1031        yt: f64,
1032        yc: f64,
1033        s: f64,
1034    ) -> f64 {
1035        self.plies
1036            .iter()
1037            .map(|p| {
1038                let (angle, t, e1, e2, g12, nu12) = *p;
1039                let nu21 = nu12 * e2 / e1;
1040                let denom = 1.0 - nu12 * nu21;
1041                let q11 = e1 / denom;
1042                let q22 = e2 / denom;
1043                let q12 = nu12 * e2 / denom;
1044                let q66 = g12;
1045                let c = angle.to_radians().cos();
1046                let sc = angle.to_radians().sin();
1047                let c2 = c * c;
1048                let s2 = sc * sc;
1049                let c4 = c2 * c2;
1050                let s4 = s2 * s2;
1051                let q11b = q11 * c4 + 2.0 * (q12 + 2.0 * q66) * s2 * c2 + q22 * s4;
1052                let q22b = q11 * s4 + 2.0 * (q12 + 2.0 * q66) * s2 * c2 + q22 * c4;
1053                let q66b = (q11 + q22 - 2.0 * q12 - 2.0 * q66) * s2 * c2 + q66 * (c4 + s4);
1054                let sigma1 = q11b * nx / t;
1055                let sigma2 = q22b * ny / t;
1056                let tau = q66b * nxy / t;
1057                let f1 = 1.0 / xt - 1.0 / xc;
1058                let f2 = 1.0 / yt - 1.0 / yc;
1059                let f11 = 1.0 / (xt * xc);
1060                let f22 = 1.0 / (yt * yc);
1061                let f66 = 1.0 / (s * s);
1062                f1 * sigma1
1063                    + f2 * sigma2
1064                    + f11 * sigma1.powi(2)
1065                    + f22 * sigma2.powi(2)
1066                    + f66 * tau.powi(2)
1067            })
1068            .fold(f64::NEG_INFINITY, f64::max)
1069    }
1070    /// Delamination onset criterion: Ye delamination criterion for interlaminar stresses.
1071    /// Returns reserve factor (>1 = safe).
1072    pub fn ye_delamination_rf(
1073        &self,
1074        sigma_z: f64,
1075        tau_xz: f64,
1076        tau_yz: f64,
1077        zzt: f64,
1078        szx: f64,
1079        szy: f64,
1080    ) -> f64 {
1081        let fi = (sigma_z / zzt).powi(2) + (tau_xz / szx).powi(2) + (tau_yz / szy).powi(2);
1082        if fi > 0.0 {
1083            1.0 / fi.sqrt()
1084        } else {
1085            f64::INFINITY
1086        }
1087    }
1088}
1089/// Aircraft fatigue load spectrum (ground-air-ground cycle model).
1090pub struct FatigueSpectrum {
1091    /// Number of design service goals (flight hours).
1092    pub dsg_hours: f64,
1093    /// Flights per hour (typical utilization).
1094    pub flights_per_hour: f64,
1095    /// Maneuver load factor exceedance table: (g_level, exceedances_per_1000_hrs).
1096    pub exceedance_data: Vec<(f64, f64)>,
1097    /// Gust spectrum intensity σ_g (g).
1098    pub gust_sigma: f64,
1099}
1100impl FatigueSpectrum {
1101    /// Create a standard medium transport aircraft spectrum.
1102    pub fn medium_transport() -> Self {
1103        FatigueSpectrum {
1104            dsg_hours: 120_000.0,
1105            flights_per_hour: 0.5,
1106            exceedance_data: vec![
1107                (1.0, 20_000.0),
1108                (1.5, 2_000.0),
1109                (2.0, 200.0),
1110                (2.5, 20.0),
1111                (3.0, 2.0),
1112                (3.5, 0.2),
1113            ],
1114            gust_sigma: 0.3,
1115        }
1116    }
1117    /// Total number of flights over DSG.
1118    pub fn total_flights(&self) -> f64 {
1119        self.dsg_hours * self.flights_per_hour
1120    }
1121    /// Exceedances at given g-level over DSG (interpolated from table).
1122    pub fn exceedances_at(&self, g_level: f64) -> f64 {
1123        if self.exceedance_data.is_empty() {
1124            return 0.0;
1125        }
1126        if g_level <= self.exceedance_data[0].0 {
1127            return self.exceedance_data[0].1 * self.dsg_hours / 1000.0;
1128        }
1129        for i in 0..self.exceedance_data.len() - 1 {
1130            let (g1, e1) = self.exceedance_data[i];
1131            let (g2, e2) = self.exceedance_data[i + 1];
1132            if g_level >= g1 && g_level <= g2 {
1133                let frac = (g_level - g1) / (g2 - g1);
1134                let e_interp = e1 + frac * (e2 - e1);
1135                return e_interp * self.dsg_hours / 1000.0;
1136            }
1137        }
1138        0.0
1139    }
1140    /// Miner's rule cumulative damage from multiple load levels.
1141    /// `s_n_pairs` is `&[(stress_range, allowed_cycles_at_stress)]`.
1142    pub fn miners_rule_damage(&self, s_n_pairs: &[(f64, f64)]) -> f64 {
1143        s_n_pairs
1144            .iter()
1145            .map(|(ni, ni_allowed)| {
1146                if *ni_allowed > 0.0 {
1147                    ni / ni_allowed
1148                } else {
1149                    f64::INFINITY
1150                }
1151            })
1152            .sum()
1153    }
1154}
1155/// Space environment degradation: radiation darkening, outgassing, atomic oxygen.
1156pub struct SpaceEnvironmentEffects {
1157    /// Material designation.
1158    pub designation: String,
1159    /// Total ionizing dose (TID) threshold for property change (Gy).
1160    pub tid_threshold: f64,
1161    /// Optical absorptance change per Mrad (1 Mrad = 10 kGy).
1162    pub d_alpha_per_mrad: f64,
1163    /// Outgassing total mass loss fraction (TML, %).
1164    pub tml_percent: f64,
1165    /// Collected volatile condensable material (CVCM, %).
1166    pub cvcm_percent: f64,
1167    /// Atomic oxygen (AO) erosion yield (cm³/atom).
1168    pub ao_erosion_yield: f64,
1169    /// AO fluence at LEO per year (atoms/cm²/year).
1170    pub ao_fluence_leo: f64,
1171    /// Material initial absorptance α0.
1172    pub alpha_initial: f64,
1173    /// Material emissivity ε.
1174    pub emissivity: f64,
1175}
1176impl SpaceEnvironmentEffects {
1177    /// Kapton HN polyimide space environment properties.
1178    pub fn kapton_hn() -> Self {
1179        SpaceEnvironmentEffects {
1180            designation: "Kapton HN".to_string(),
1181            tid_threshold: 1.0e6,
1182            d_alpha_per_mrad: 0.01,
1183            tml_percent: 0.98,
1184            cvcm_percent: 0.01,
1185            ao_erosion_yield: 3.0e-24,
1186            ao_fluence_leo: 8.0e20,
1187            alpha_initial: 0.32,
1188            emissivity: 0.64,
1189        }
1190    }
1191    /// SolarBlack paint (high absorptance thermal control).
1192    pub fn solar_black_paint() -> Self {
1193        SpaceEnvironmentEffects {
1194            designation: "Solar Black Paint".to_string(),
1195            tid_threshold: 5.0e5,
1196            d_alpha_per_mrad: 0.005,
1197            tml_percent: 0.20,
1198            cvcm_percent: 0.02,
1199            ao_erosion_yield: 1.0e-25,
1200            ao_fluence_leo: 8.0e20,
1201            alpha_initial: 0.96,
1202            emissivity: 0.87,
1203        }
1204    }
1205    /// Absorptance after radiation dose `dose_gy` (Gy).
1206    pub fn radiation_darkened_absorptance(&self, dose_gy: f64) -> f64 {
1207        let dose_mrad = dose_gy / 10_000.0;
1208        (self.alpha_initial + self.d_alpha_per_mrad * dose_mrad).min(1.0)
1209    }
1210    /// Atomic oxygen erosion depth (μm) after `t_years` years in LEO.
1211    pub fn ao_erosion_depth_um(&self, t_years: f64, density_g_cm3: f64) -> f64 {
1212        let fluence = self.ao_fluence_leo * t_years;
1213        let mass_loss = self.ao_erosion_yield * fluence;
1214        mass_loss * density_g_cm3 * 10_000.0
1215    }
1216    /// Check if outgassing meets NASA ASTM E595 requirement (TML < 1%, CVCM < 0.1%).
1217    pub fn passes_outgassing(&self) -> bool {
1218        self.tml_percent < 1.0 && self.cvcm_percent < 0.1
1219    }
1220    /// Solar absorptance-to-emissivity ratio α/ε (thermal balance parameter).
1221    pub fn alpha_over_epsilon(&self) -> f64 {
1222        self.alpha_initial / self.emissivity
1223    }
1224}
1225/// Damage tolerance analysis: initial flaw, inspection interval, repair threshold.
1226pub struct DamageToleranceFlaw {
1227    /// Initial detectable flaw size a_i (m).
1228    pub a_initial: f64,
1229    /// Critical flaw size a_cr (m) at design ultimate load.
1230    pub a_critical: f64,
1231    /// Repair threshold a_r (m).
1232    pub a_repair: f64,
1233    /// Inspection interval (flight hours).
1234    pub inspection_interval: f64,
1235    /// Probability of detection at repair threshold (0–1).
1236    pub pod_at_repair: f64,
1237}
1238impl DamageToleranceFlaw {
1239    /// Create a standard damage tolerance scenario.
1240    pub fn new(a_initial: f64, a_critical: f64, inspection_interval: f64) -> Self {
1241        DamageToleranceFlaw {
1242            a_initial,
1243            a_critical,
1244            a_repair: a_critical / 2.0,
1245            inspection_interval,
1246            pod_at_repair: 0.9,
1247        }
1248    }
1249    /// Check if the flaw size `a` exceeds the repair threshold.
1250    pub fn requires_repair(&self, a: f64) -> bool {
1251        a >= self.a_repair
1252    }
1253    /// Check if the flaw size `a` exceeds the critical size.
1254    pub fn is_critical(&self, a: f64) -> bool {
1255        a >= self.a_critical
1256    }
1257    /// Crack growth life factor: ratio of critical to initial flaw size.
1258    pub fn life_factor(&self) -> f64 {
1259        self.a_critical / self.a_initial
1260    }
1261    /// Required number of inspection intervals before crack becomes critical.
1262    /// (Assumes linear growth rate for simplicity.)
1263    pub fn inspections_before_critical(&self, growth_rate_per_hour: f64) -> f64 {
1264        if growth_rate_per_hour <= 0.0 {
1265            return f64::INFINITY;
1266        }
1267        let time_to_critical = (self.a_critical - self.a_initial) / growth_rate_per_hour;
1268        time_to_critical / self.inspection_interval
1269    }
1270}
1271/// Extended CMC model: matrix cracking, fiber pullout, BN interphase.
1272pub struct CeramicMatrixCompositeExtended {
1273    /// Fiber diameter df (μm).
1274    pub df: f64,
1275    /// Fiber volume fraction Vf.
1276    pub vf: f64,
1277    /// Fiber modulus Ef (GPa).
1278    pub ef: f64,
1279    /// Matrix modulus Em (GPa).
1280    pub em: f64,
1281    /// Interface sliding stress τi (MPa).
1282    pub tau_i: f64,
1283    /// Fiber strength σfu (MPa).
1284    pub sigma_fu: f64,
1285    /// Matrix cracking stress σmc (MPa).
1286    pub sigma_mc: f64,
1287    /// BN interphase thickness (nm).
1288    pub bn_thickness_nm: f64,
1289    /// Fiber pullout length lp (mm).
1290    pub pullout_length: f64,
1291    /// Composite density (kg/m³).
1292    pub density: f64,
1293}
1294impl CeramicMatrixCompositeExtended {
1295    /// Hi-Nicalon SiC/SiC with BN interphase.
1296    pub fn hi_nicalon_sic_sic() -> Self {
1297        CeramicMatrixCompositeExtended {
1298            df: 14.0,
1299            vf: 0.40,
1300            ef: 270.0,
1301            em: 350.0,
1302            tau_i: 50.0,
1303            sigma_fu: 2000.0,
1304            sigma_mc: 150.0,
1305            bn_thickness_nm: 300.0,
1306            pullout_length: 0.5,
1307            density: 2600.0,
1308        }
1309    }
1310    /// Tyranno SA C/SiC.
1311    pub fn tyranno_sa() -> Self {
1312        CeramicMatrixCompositeExtended {
1313            df: 7.5,
1314            vf: 0.45,
1315            ef: 340.0,
1316            em: 350.0,
1317            tau_i: 30.0,
1318            sigma_fu: 2300.0,
1319            sigma_mc: 120.0,
1320            bn_thickness_nm: 200.0,
1321            pullout_length: 1.0,
1322            density: 2800.0,
1323        }
1324    }
1325    /// Composite modulus (rule of mixtures) (GPa).
1326    pub fn composite_modulus(&self) -> f64 {
1327        self.ef * self.vf + self.em * (1.0 - self.vf)
1328    }
1329    /// Matrix cracking stress using ACK model (MPa).
1330    /// σ_mc = (12 * τi * Ef * Vf^2 * Γm * Ec / (Em^2 * df * (1-Vf)))^(1/3)
1331    pub fn ack_matrix_cracking_stress(&self, gamma_m: f64) -> f64 {
1332        let ec = self.composite_modulus();
1333        let df_m = self.df * 1.0e-6;
1334        let num = 12.0 * self.tau_i * self.ef * self.vf.powi(2) * gamma_m * ec;
1335        let den = self.em.powi(2) * df_m * 1000.0 * (1.0 - self.vf);
1336        if den < 1.0e-30 {
1337            return 0.0;
1338        }
1339        (num / den).powf(1.0 / 3.0)
1340    }
1341    /// Fiber pullout energy per unit area (J/m²).
1342    pub fn pullout_energy(&self) -> f64 {
1343        let df_m = self.df * 1.0e-6;
1344        let lp = self.pullout_length * 1.0e-3;
1345        self.vf * self.tau_i * lp.powi(2) / df_m
1346    }
1347    /// Composite ultimate tensile strength (MPa) using GLS model.
1348    pub fn ultimate_strength(&self) -> f64 {
1349        self.vf * self.sigma_fu * (1.0 - self.vf).powf(0.5)
1350    }
1351    /// Thermal shock resistance parameter R' (K) = σ_mc * k / (E * CTE).
1352    pub fn thermal_shock_r_prime(&self, k: f64, cte: f64) -> f64 {
1353        let ec = self.composite_modulus();
1354        self.sigma_mc * k / (ec * 1.0e9 * cte)
1355    }
1356}
1357/// A single lamina (ply) in a composite laminate.
1358#[derive(Debug, Clone)]
1359pub struct CfrpPly {
1360    /// Ply angle (degrees).
1361    pub angle_deg: f64,
1362    /// Ply thickness (mm).
1363    pub thickness: f64,
1364    /// Longitudinal modulus E1 (GPa).
1365    pub e1: f64,
1366    /// Transverse modulus E2 (GPa).
1367    pub e2: f64,
1368    /// In-plane shear modulus G12 (GPa).
1369    pub g12: f64,
1370    /// Major Poisson's ratio ν12.
1371    pub nu12: f64,
1372    /// Longitudinal tensile strength F1t (MPa).
1373    pub f1t: f64,
1374    /// Transverse tensile strength F2t (MPa).
1375    pub f2t: f64,
1376    /// In-plane shear strength F6 (MPa).
1377    pub f6: f64,
1378}
1379impl CfrpPly {
1380    /// Create a standard IM7/8552 carbon fiber epoxy ply.
1381    pub fn im7_8552(angle_deg: f64, thickness: f64) -> Self {
1382        CfrpPly {
1383            angle_deg,
1384            thickness,
1385            e1: 161.0,
1386            e2: 11.4,
1387            g12: 5.17,
1388            nu12: 0.32,
1389            f1t: 2560.0,
1390            f2t: 73.0,
1391            f6: 90.0,
1392        }
1393    }
1394    /// Transformed stiffness matrix Q̄ in global coordinates (GPa).
1395    /// Returns \[Q11bar, Q22bar, Q12bar, Q66bar, Q16bar, Q26bar\].
1396    pub fn transformed_stiffness(&self) -> [f64; 6] {
1397        let theta = self.angle_deg.to_radians();
1398        let c = theta.cos();
1399        let s = theta.sin();
1400        let nu21 = self.nu12 * self.e2 / self.e1;
1401        let denom = 1.0 - self.nu12 * nu21;
1402        let q11 = self.e1 / denom;
1403        let q22 = self.e2 / denom;
1404        let q12 = self.nu12 * self.e2 / denom;
1405        let q66 = self.g12;
1406        let c2 = c * c;
1407        let s2 = s * s;
1408        let c4 = c2 * c2;
1409        let s4 = s2 * s2;
1410        let cs = c * s;
1411        let q11b = q11 * c4 + 2.0 * (q12 + 2.0 * q66) * s2 * c2 + q22 * s4;
1412        let q22b = q11 * s4 + 2.0 * (q12 + 2.0 * q66) * s2 * c2 + q22 * c4;
1413        let q12b = (q11 + q22 - 4.0 * q66) * s2 * c2 + q12 * (c4 + s4);
1414        let q66b = (q11 + q22 - 2.0 * q12 - 2.0 * q66) * s2 * c2 + q66 * (c4 + s4);
1415        let q16b = (q11 - q12 - 2.0 * q66) * c * s2 * c - (q22 - q12 - 2.0 * q66) * cs * s2;
1416        let q26b = (q11 - q12 - 2.0 * q66) * cs * s2 - (q22 - q12 - 2.0 * q66) * c2 * cs;
1417        [q11b, q22b, q12b, q66b, q16b, q26b]
1418    }
1419}
1420/// Adhesive joint model: lap shear, T-peel, climbing drum peel.
1421pub struct AdhesiveJoint {
1422    /// Adhesive designation.
1423    pub designation: String,
1424    /// Adhesive shear modulus G (MPa).
1425    pub g_adhesive: f64,
1426    /// Adhesive tensile modulus E (MPa).
1427    pub e_adhesive: f64,
1428    /// Adhesive thickness t (mm).
1429    pub t_adhesive: f64,
1430    /// Mode I fracture energy GIc (J/m²).
1431    pub g_ic: f64,
1432    /// Mode II fracture energy GIIc (J/m²).
1433    pub g_iic: f64,
1434    /// Shear strength τ_u (MPa).
1435    pub tau_u: f64,
1436    /// Tensile strength σ_u (MPa).
1437    pub sigma_u: f64,
1438    /// Overlap length l (mm).
1439    pub overlap_length: f64,
1440}
1441impl AdhesiveJoint {
1442    /// FM 300 film adhesive (aerospace structural).
1443    pub fn fm300(t_adhesive: f64, overlap_length: f64) -> Self {
1444        AdhesiveJoint {
1445            designation: "FM 300".to_string(),
1446            g_adhesive: 650.0,
1447            e_adhesive: 2100.0,
1448            t_adhesive,
1449            g_ic: 1500.0,
1450            g_iic: 2500.0,
1451            tau_u: 40.0,
1452            sigma_u: 45.0,
1453            overlap_length,
1454        }
1455    }
1456    /// EC-3448 paste adhesive.
1457    pub fn ec3448(t_adhesive: f64, overlap_length: f64) -> Self {
1458        AdhesiveJoint {
1459            designation: "EC-3448".to_string(),
1460            g_adhesive: 400.0,
1461            e_adhesive: 1800.0,
1462            t_adhesive,
1463            g_ic: 800.0,
1464            g_iic: 1500.0,
1465            tau_u: 25.0,
1466            sigma_u: 30.0,
1467            overlap_length,
1468        }
1469    }
1470    /// Volkersen shear stress distribution in single-lap joint.
1471    /// Returns peak shear stress at joint end (MPa).
1472    pub fn volkersen_peak_shear(&self, e_sub: f64, t_sub: f64, load_n_per_mm: f64) -> f64 {
1473        let omega = ((self.g_adhesive / self.t_adhesive) * (2.0 / (e_sub * t_sub))).sqrt();
1474        let l = self.overlap_length;
1475        let cosh_val = (omega * l).cosh();
1476        let sinh_val = (omega * l).sinh();
1477        if sinh_val < 1.0e-12 {
1478            return load_n_per_mm / l;
1479        }
1480        load_n_per_mm * omega * cosh_val / sinh_val
1481    }
1482    /// T-peel fracture energy (J/m²) required to propagate delamination.
1483    pub fn t_peel_energy(&self, peel_force_n_per_mm: f64) -> f64 {
1484        2.0 * peel_force_n_per_mm * 1000.0
1485    }
1486    /// Climbing drum peel mode I energy release rate G_I (J/m²).
1487    pub fn climbing_drum_g1(&self, m_drum: f64, r_drum: f64) -> f64 {
1488        m_drum / r_drum
1489    }
1490    /// Reserve factor against shear failure.
1491    pub fn rf_shear(&self, applied_shear: f64) -> f64 {
1492        self.tau_u / applied_shear.max(1.0e-15)
1493    }
1494}