1#[allow(unused_imports)]
6use super::functions::*;
7use std::f64::consts::PI;
8
9pub struct CrashAbsorber {
11 pub designation: String,
13 pub crush_stress: f64,
15 pub trigger_factor: f64,
17 pub area: f64,
19 pub length: f64,
21 pub densification_strain: f64,
23 pub density: f64,
25}
26impl CrashAbsorber {
27 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 pub fn peak_force(&self) -> f64 {
41 self.crush_stress * self.area * self.trigger_factor
42 }
43 pub fn mean_crush_force(&self) -> f64 {
45 self.crush_stress * self.area
46 }
47 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 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 pub fn stroke_for_energy(&self, e: f64) -> f64 {
59 e / self.mean_crush_force()
60 }
61}
62pub struct SuperalloyMaterial {
64 pub designation: String,
66 pub sigma_02_650: f64,
68 pub uts_650: f64,
70 pub creep_rupture_life: f64,
72 pub larson_miller_c: f64,
74 pub gamma_prime_fraction: f64,
76 pub oxidation_kp: f64,
78 pub density: f64,
80}
81impl SuperalloyMaterial {
82 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 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 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 pub fn oxidation_mass_gain(&self, t_hours: f64) -> f64 {
108 (self.oxidation_kp * t_hours).sqrt()
109 }
110}
111pub struct CmcMaterial {
113 pub designation: String,
115 pub matrix_cracking_stress: f64,
117 pub uts: f64,
119 pub modulus: f64,
121 pub thermal_conductivity: f64,
123 pub cte: f64,
125 pub max_temperature: f64,
127 pub k_ic: f64,
129}
130impl CmcMaterial {
131 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 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 pub fn critical_crack_length(&self, sigma: f64, y: f64) -> f64 {
159 (self.k_ic / (y * sigma)).powi(2) / PI
160 }
161 pub fn thermal_shock_resistance(&self, nu: f64) -> f64 {
163 self.uts * (1.0 - nu) / (self.modulus * 1e3 * self.cte)
164 }
165}
166pub struct HoneycombSandwich {
168 pub cell_size: f64,
170 pub core_density: f64,
172 pub g_rt: f64,
174 pub g_lt: f64,
176 pub face_thickness: f64,
178 pub core_thickness: f64,
180 pub face_modulus: f64,
182}
183impl HoneycombSandwich {
184 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 pub fn total_thickness(&self) -> f64 {
205 2.0 * self.face_thickness + self.core_thickness
206 }
207 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 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 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}
224pub struct TitaniumAlloyExtended {
226 pub designation: String,
228 pub sigma_y_rt: f64,
230 pub sigma_y_300: f64,
232 pub creep_q: f64,
234 pub creep_a: f64,
236 pub creep_n: f64,
238 pub kp_600: f64,
240 pub e_rt: f64,
242 pub de_dt: f64,
244 pub density: f64,
246}
247impl TitaniumAlloyExtended {
248 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 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 pub fn youngs_modulus_at(&self, t_celsius: f64) -> f64 {
280 (self.e_rt + self.de_dt * t_celsius).max(50.0)
281 }
282 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 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 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 pub fn oxidation_gain_600(&self, t_hours: f64) -> f64 {
302 (self.kp_600 * t_hours).sqrt()
303 }
304}
305pub struct CfrpLaminate {
307 pub plies: Vec<CfrpPly>,
309 pub thickness: f64,
311}
312impl CfrpLaminate {
313 pub fn new(plies: Vec<CfrpPly>) -> Self {
315 let thickness = plies.iter().map(|p| p.thickness).sum();
316 CfrpLaminate { plies, thickness }
317 }
318 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 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 pub fn effective_ex(&self) -> f64 {
345 let [a11, _a22, a12, _a66] = self.a_matrix();
346 (a11 - a12 * a12 / a11) / self.thickness
347 }
348 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}
370pub struct AerogelInsulation {
372 pub k_solid: f64,
374 pub k_rad_25: f64,
376 pub porosity: f64,
378 pub pore_diameter_nm: f64,
380 pub density: f64,
382 pub temperature: f64,
384 pub k_gas_stp: f64,
386}
387impl AerogelInsulation {
388 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 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 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 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 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 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}
438pub struct ThermalProtection {
440 pub designation: String,
442 pub k_room: f64,
444 pub k_hot: f64,
446 pub cp_room: f64,
448 pub emissivity: f64,
450 pub max_temp: f64,
452 pub density: f64,
454}
455impl ThermalProtection {
456 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 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 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 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}
484pub struct CarbonCarbonComposite {
486 pub vf: f64,
488 pub k_axial: f64,
490 pub k_transverse: f64,
492 pub strength_rt: f64,
494 pub strength_2000: f64,
496 pub sic_coat_thickness: f64,
498 pub oxidation_onset_temp: f64,
500 pub kox_600: f64,
502 pub density: f64,
504}
505impl CarbonCarbonComposite {
506 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 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 pub fn effective_conductivity(&self) -> f64 {
536 self.vf * self.k_axial + (1.0 - self.vf) * self.k_transverse
537 }
538 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 pub fn coating_protection_factor(&self) -> f64 {
553 1.0 - (-self.sic_coat_thickness / 100.0).exp()
554 }
555 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}
562pub struct FoamCoreSandwich {
564 pub core_designation: String,
566 pub core_density: f64,
568 pub ec: f64,
570 pub gc: f64,
572 pub sigma_c: f64,
574 pub tau_c: f64,
576 pub tf: f64,
578 pub ef: f64,
580 pub sigma_f: f64,
582 pub tc: f64,
584}
585impl FoamCoreSandwich {
586 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 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 pub fn total_thickness(&self) -> f64 {
618 2.0 * self.tf + self.tc
619 }
620 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 pub fn shear_stiffness(&self) -> f64 {
627 self.gc * self.tc
628 }
629 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 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 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}
642pub struct NickelSuperalloyExtended {
644 pub designation: String,
646 pub gamma_prime_vf: f64,
648 pub misfit: f64,
650 pub uts_rt: f64,
652 pub uts_1000: f64,
654 pub creep_life_ref: f64,
656 pub lm_c: f64,
658 pub kp_1000: f64,
660 pub bond_coat_thickness: f64,
662 pub density: f64,
664}
665impl NickelSuperalloyExtended {
666 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 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 pub fn larson_miller_parameter(&self, t_kelvin: f64, tr_hours: f64) -> f64 {
698 t_kelvin * (self.lm_c + tr_hours.log10())
699 }
700 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 pub fn oxidation_gain(&self, t_hours: f64) -> f64 {
709 (self.kp_1000 * t_hours).sqrt()
710 }
711 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 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}
732pub struct AblativeHeatShield {
734 pub designation: String,
736 pub h_eff: f64,
738 pub k_char: f64,
740 pub k_virgin: f64,
742 pub rho_char: f64,
744 pub rho_virgin: f64,
746 pub cp_char: f64,
748 pub pyrolysis_temp: f64,
750}
751impl AblativeHeatShield {
752 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 pub fn recession_rate(&self, q_w: f64) -> f64 {
768 q_w / (self.rho_virgin * self.h_eff * 1e6)
769 }
770 pub fn char_thickness(&self, q_w: f64, t: f64) -> f64 {
772 self.recession_rate(q_w) * t
773 }
774 pub fn mass_loss(&self, q_w: f64, t: f64) -> f64 {
776 self.rho_virgin * self.char_thickness(q_w, t)
777 }
778}
779pub struct TitaniumAlloy {
781 pub designation: String,
783 pub e: f64,
785 pub sigma_y: f64,
787 pub uts: f64,
789 pub delta_k_th: f64,
791 pub paris_c: f64,
793 pub paris_m: f64,
795 pub density: f64,
797 pub alpha_fraction: f64,
799}
800impl TitaniumAlloy {
801 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 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 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 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}
843pub struct AblationCharModel {
845 pub rho_virgin: f64,
847 pub rho_char: f64,
849 pub h_eff: f64,
851 pub k_char: f64,
853 pub k_virgin: f64,
855 pub cp_virgin: f64,
857 pub cp_char: f64,
859 pub t_pyrolysis_start: f64,
861 pub t_pyrolysis_end: f64,
863 pub h_pyrolysis: f64,
865}
866impl AblationCharModel {
867 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 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 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 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 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 pub fn recession_velocity(&self, q_w: f64) -> f64 {
919 q_w / (self.rho_virgin * self.h_eff * 1.0e6)
920 }
921 pub fn char_thickness_at_time(&self, q_w: f64, t: f64) -> f64 {
923 self.recession_velocity(q_w) * t
924 }
925 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 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}
938pub struct CompositeLaminateClt {
940 pub plies: Vec<(f64, f64, f64, f64, f64, f64)>,
942}
943impl CompositeLaminateClt {
944 pub fn new(plies: Vec<(f64, f64, f64, f64, f64, f64)>) -> Self {
947 CompositeLaminateClt { plies }
948 }
949 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 pub fn total_thickness_mm(&self) -> f64 {
967 self.plies.iter().map(|p| p.1).sum()
968 }
969 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 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 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 #[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 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}
1089pub struct FatigueSpectrum {
1091 pub dsg_hours: f64,
1093 pub flights_per_hour: f64,
1095 pub exceedance_data: Vec<(f64, f64)>,
1097 pub gust_sigma: f64,
1099}
1100impl FatigueSpectrum {
1101 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 pub fn total_flights(&self) -> f64 {
1119 self.dsg_hours * self.flights_per_hour
1120 }
1121 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 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}
1155pub struct SpaceEnvironmentEffects {
1157 pub designation: String,
1159 pub tid_threshold: f64,
1161 pub d_alpha_per_mrad: f64,
1163 pub tml_percent: f64,
1165 pub cvcm_percent: f64,
1167 pub ao_erosion_yield: f64,
1169 pub ao_fluence_leo: f64,
1171 pub alpha_initial: f64,
1173 pub emissivity: f64,
1175}
1176impl SpaceEnvironmentEffects {
1177 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 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 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 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 pub fn passes_outgassing(&self) -> bool {
1218 self.tml_percent < 1.0 && self.cvcm_percent < 0.1
1219 }
1220 pub fn alpha_over_epsilon(&self) -> f64 {
1222 self.alpha_initial / self.emissivity
1223 }
1224}
1225pub struct DamageToleranceFlaw {
1227 pub a_initial: f64,
1229 pub a_critical: f64,
1231 pub a_repair: f64,
1233 pub inspection_interval: f64,
1235 pub pod_at_repair: f64,
1237}
1238impl DamageToleranceFlaw {
1239 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 pub fn requires_repair(&self, a: f64) -> bool {
1251 a >= self.a_repair
1252 }
1253 pub fn is_critical(&self, a: f64) -> bool {
1255 a >= self.a_critical
1256 }
1257 pub fn life_factor(&self) -> f64 {
1259 self.a_critical / self.a_initial
1260 }
1261 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}
1271pub struct CeramicMatrixCompositeExtended {
1273 pub df: f64,
1275 pub vf: f64,
1277 pub ef: f64,
1279 pub em: f64,
1281 pub tau_i: f64,
1283 pub sigma_fu: f64,
1285 pub sigma_mc: f64,
1287 pub bn_thickness_nm: f64,
1289 pub pullout_length: f64,
1291 pub density: f64,
1293}
1294impl CeramicMatrixCompositeExtended {
1295 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 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 pub fn composite_modulus(&self) -> f64 {
1327 self.ef * self.vf + self.em * (1.0 - self.vf)
1328 }
1329 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 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 pub fn ultimate_strength(&self) -> f64 {
1349 self.vf * self.sigma_fu * (1.0 - self.vf).powf(0.5)
1350 }
1351 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#[derive(Debug, Clone)]
1359pub struct CfrpPly {
1360 pub angle_deg: f64,
1362 pub thickness: f64,
1364 pub e1: f64,
1366 pub e2: f64,
1368 pub g12: f64,
1370 pub nu12: f64,
1372 pub f1t: f64,
1374 pub f2t: f64,
1376 pub f6: f64,
1378}
1379impl CfrpPly {
1380 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 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}
1420pub struct AdhesiveJoint {
1422 pub designation: String,
1424 pub g_adhesive: f64,
1426 pub e_adhesive: f64,
1428 pub t_adhesive: f64,
1430 pub g_ic: f64,
1432 pub g_iic: f64,
1434 pub tau_u: f64,
1436 pub sigma_u: f64,
1438 pub overlap_length: f64,
1440}
1441impl AdhesiveJoint {
1442 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 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 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 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 pub fn climbing_drum_g1(&self, m_drum: f64, r_drum: f64) -> f64 {
1488 m_drum / r_drum
1489 }
1490 pub fn rf_shear(&self, applied_shear: f64) -> f64 {
1492 self.tau_u / applied_shear.max(1.0e-15)
1493 }
1494}