1#![allow(dead_code)]
8#![allow(clippy::too_many_arguments)]
9
10#[derive(Debug, Clone)]
14pub struct AlloyComposition {
15 pub elements: Vec<(String, f64)>,
17 pub density: f64,
19 pub melting_range: (f64, f64),
21}
22
23impl AlloyComposition {
24 pub fn new(elements: Vec<(String, f64)>, density: f64, melting_range: (f64, f64)) -> Self {
26 Self {
27 elements,
28 density,
29 melting_range,
30 }
31 }
32
33 pub fn validate(&self) -> bool {
35 let sum: f64 = self.elements.iter().map(|(_, f)| f).sum();
36 (sum - 1.0).abs() < 1e-6
37 }
38
39 pub fn density_mixture(&self, densities: &[(String, f64)]) -> f64 {
44 let mut inv_sum = 0.0;
45 let mut frac_sum = 0.0;
46 for (sym, frac) in &self.elements {
47 if let Some((_, d)) = densities.iter().find(|(s, _)| s == sym)
48 && *d > 0.0
49 {
50 inv_sum += frac / d;
51 frac_sum += frac;
52 }
53 }
54 if inv_sum < 1e-30 || frac_sum < 1e-12 {
55 self.density
56 } else {
57 frac_sum / inv_sum
58 }
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum AlloySeries {
67 Series1xxx,
69 Series2xxx,
71 Series3xxx,
73 Series4xxx,
75 Series5xxx,
77 Series6xxx,
79 Series7xxx,
81 Series8xxx,
83 Series9xxx,
85 StainlessSteel304,
87 StainlessSteel316,
89 Inconel718,
91 TitaniumTi6Al4V,
93 Tool4140,
95 Maraging300,
97 Hastelloy,
99}
100
101#[derive(Debug, Clone)]
105pub struct AlloyMechanicalProps {
106 pub yield_strength: f64,
108 pub uts: f64,
110 pub elongation: f64,
112 pub hardness_hv: f64,
114 pub youngs_modulus: f64,
116 pub poisson_ratio: f64,
118 pub fracture_toughness: f64,
120}
121
122impl AlloyMechanicalProps {
123 pub fn new(
125 yield_strength: f64,
126 uts: f64,
127 elongation: f64,
128 hardness_hv: f64,
129 youngs_modulus: f64,
130 poisson_ratio: f64,
131 fracture_toughness: f64,
132 ) -> Self {
133 Self {
134 yield_strength,
135 uts,
136 elongation,
137 hardness_hv,
138 youngs_modulus,
139 poisson_ratio,
140 fracture_toughness,
141 }
142 }
143
144 pub fn safety_factor(&self, applied_stress: f64) -> f64 {
146 if applied_stress.abs() < 1e-15 {
147 f64::INFINITY
148 } else {
149 self.yield_strength / applied_stress
150 }
151 }
152
153 pub fn is_brittle(&self) -> bool {
155 self.elongation < 5.0
156 }
157}
158
159pub struct HallPetch;
163
164impl HallPetch {
165 pub fn yield_strength(sigma_0: f64, k: f64, grain_size: f64) -> f64 {
169 sigma_0 + k / grain_size.max(1e-30).sqrt()
170 }
171
172 pub fn grain_size_from_yield(sigma_y: f64, sigma_0: f64, k: f64) -> f64 {
174 let diff = sigma_y - sigma_0;
175 if diff.abs() < 1e-15 {
176 return f64::INFINITY;
177 }
178 (k / diff).powi(2)
179 }
180}
181
182pub struct Strengthening;
186
187impl Strengthening {
188 pub fn solid_solution_strengthening(c: f64, k_ss: f64) -> f64 {
192 k_ss * c.max(0.0).powf(2.0 / 3.0)
193 }
194
195 pub fn precipitation_hardening(particle_spacing: f64, shear_modulus: f64, burgers: f64) -> f64 {
202 0.13 * shear_modulus * burgers / particle_spacing.max(1e-30)
203 }
204
205 pub fn work_hardening(eps_p: f64, k: f64, n: f64) -> f64 {
209 k * eps_p.max(0.0).powf(n)
210 }
211
212 pub fn combined_strengthening(ss: f64, ph: f64, wh: f64, gb: f64) -> f64 {
217 ss + ph + wh + gb
218 }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum BinaryPhase {
226 Liquid,
228 Alpha,
230 Beta,
232 AlphaPlusBeta,
234 Eutectic,
236}
237
238pub struct PhaseDiagram;
240
241impl PhaseDiagram {
242 pub fn phase_at_composition(
248 x: f64,
249 temp: f64,
250 eutectic: (f64, f64),
251 liquidus: [f64; 2],
252 ) -> BinaryPhase {
253 let (x_e, t_e) = eutectic;
254 let t_a = liquidus[0];
255 let t_b = liquidus[1];
256
257 let t_liq_a = if x_e > 1e-12 {
259 t_a - (t_a - t_e) * x / x_e
260 } else {
261 t_a
262 };
263
264 let t_liq_b = if (1.0 - x_e) > 1e-12 {
266 t_b - (t_b - t_e) * (1.0 - x) / (1.0 - x_e)
267 } else {
268 t_b
269 };
270
271 let t_liquidus = if x <= x_e { t_liq_a } else { t_liq_b };
272
273 if temp > t_liquidus {
274 BinaryPhase::Liquid
275 } else if (temp - t_e).abs() < 5.0 && (x - x_e).abs() < 0.02 {
276 BinaryPhase::Eutectic
277 } else if temp < t_e {
278 BinaryPhase::AlphaPlusBeta
279 } else if x < x_e {
280 BinaryPhase::Alpha
281 } else {
282 BinaryPhase::Beta
283 }
284 }
285}
286
287pub fn alloy_thermal_conductivity(k1: f64, k2: f64, x: f64) -> f64 {
293 (1.0 - x) * k1 + x * k2
294}
295
296pub fn alloy_specific_heat(cp_components: &[(f64, f64)]) -> f64 {
300 cp_components.iter().map(|(f, cp)| f * cp).sum()
301}
302
303pub fn pitting_resistance_equivalent(cr: f64, mo: f64, n_pct: f64) -> f64 {
309 cr + 3.3 * mo + 16.0 * n_pct
310}
311
312pub fn galvanic_corrosion_risk(e1: f64, e2: f64) -> &'static str {
316 let delta = (e1 - e2).abs();
317 if delta < 0.1 {
318 "negligible"
319 } else if delta < 0.25 {
320 "low"
321 } else if delta < 0.5 {
322 "moderate"
323 } else {
324 "high"
325 }
326}
327
328pub struct AluminumAlloyT6;
332
333impl AluminumAlloyT6 {
334 pub fn properties(series: AlloySeries) -> AlloyMechanicalProps {
336 match series {
337 AlloySeries::Series2xxx => {
338 AlloyMechanicalProps::new(324.0, 469.0, 10.0, 130.0, 73.1, 0.33, 37.0)
339 }
340 AlloySeries::Series6xxx => {
341 AlloyMechanicalProps::new(276.0, 310.0, 12.0, 95.0, 68.9, 0.33, 29.0)
342 }
343 AlloySeries::Series7xxx => {
344 AlloyMechanicalProps::new(503.0, 572.0, 11.0, 150.0, 71.7, 0.33, 29.0)
345 }
346 _ => AlloyMechanicalProps::new(100.0, 150.0, 8.0, 50.0, 69.0, 0.33, 20.0),
347 }
348 }
349}
350
351pub struct NickelSuperalloy;
355
356impl NickelSuperalloy {
357 pub fn creep_rate(sigma: f64, temp: f64, a_coeff: f64, n_exp: f64, q_activation: f64) -> f64 {
366 const R: f64 = 8.314; a_coeff * sigma.powf(n_exp) * (-q_activation / (R * temp.max(1e-3))).exp()
368 }
369
370 pub fn oxidation_mass_gain(k_p: f64, t: f64) -> f64 {
374 (k_p * t.max(0.0)).sqrt()
375 }
376}
377
378pub struct WeldabilityIndex;
382
383impl WeldabilityIndex {
384 pub fn carbon_equivalent_iiw(
390 c: f64,
391 mn: f64,
392 cr: f64,
393 mo: f64,
394 v: f64,
395 ni: f64,
396 cu: f64,
397 ) -> f64 {
398 c + mn / 6.0 + (cr + mo + v) / 5.0 + (ni + cu) / 15.0
399 }
400
401 pub fn weldability_class(ce: f64) -> &'static str {
403 if ce < 0.35 {
404 "excellent"
405 } else if ce < 0.45 {
406 "good"
407 } else if ce < 0.60 {
408 "fair"
409 } else {
410 "poor"
411 }
412 }
413}
414
415#[cfg(test)]
418mod tests {
419 use super::*;
420
421 fn stainless_304() -> AlloyComposition {
422 AlloyComposition::new(
423 vec![
424 ("Fe".to_string(), 0.69),
425 ("Cr".to_string(), 0.19),
426 ("Ni".to_string(), 0.10),
427 ("Mn".to_string(), 0.02),
428 ],
429 7900.0,
430 (1673.0, 1723.0),
431 )
432 }
433
434 fn al6061_t6() -> AlloyMechanicalProps {
435 AlloyMechanicalProps::new(276.0, 310.0, 12.0, 95.0, 68.9, 0.33, 29.0)
436 }
437
438 #[test]
439 fn test_alloy_composition_validate_valid() {
440 let alloy = stainless_304();
441 assert!(alloy.validate());
442 }
443
444 #[test]
445 fn test_alloy_composition_validate_invalid() {
446 let alloy = AlloyComposition::new(
447 vec![("Fe".to_string(), 0.5), ("Cr".to_string(), 0.3)],
448 7900.0,
449 (1673.0, 1723.0),
450 );
451 assert!(!alloy.validate());
452 }
453
454 #[test]
455 fn test_density_mixture_rule_of_mixtures() {
456 let alloy = AlloyComposition::new(
457 vec![("A".to_string(), 0.5), ("B".to_string(), 0.5)],
458 0.0,
459 (1000.0, 1200.0),
460 );
461 let densities = vec![("A".to_string(), 2000.0), ("B".to_string(), 4000.0)];
462 let d = alloy.density_mixture(&densities);
463 assert!((d - 2666.67).abs() < 1.0, "d = {:.6}", d);
465 }
466
467 #[test]
468 fn test_density_mixture_missing_element() {
469 let alloy = AlloyComposition::new(vec![("X".to_string(), 1.0)], 7000.0, (1000.0, 1100.0));
470 let densities = vec![("Fe".to_string(), 7874.0)];
471 let d = alloy.density_mixture(&densities);
473 assert!((d - 7000.0).abs() < 1e-6);
474 }
475
476 #[test]
477 fn test_safety_factor_normal() {
478 let props = al6061_t6();
479 let sf = props.safety_factor(138.0);
480 assert!((sf - 2.0).abs() < 0.01);
481 }
482
483 #[test]
484 fn test_safety_factor_zero_stress() {
485 let props = al6061_t6();
486 assert!(props.safety_factor(0.0).is_infinite());
487 }
488
489 #[test]
490 fn test_is_brittle_ductile() {
491 let props = al6061_t6();
492 assert!(!props.is_brittle());
493 }
494
495 #[test]
496 fn test_is_brittle_true() {
497 let props = AlloyMechanicalProps::new(600.0, 700.0, 2.0, 700.0, 210.0, 0.28, 50.0);
498 assert!(props.is_brittle());
499 }
500
501 #[test]
502 fn test_hall_petch_yield_strength() {
503 let sy = HallPetch::yield_strength(50.0, 0.5, 0.25);
505 assert!((sy - 51.0).abs() < 1e-9, "sy = {:.6}", sy);
506 }
507
508 #[test]
509 fn test_hall_petch_grain_size_inversion() {
510 let sigma_0 = 50.0;
511 let k = 0.5;
512 let d0 = 1.0e-6;
513 let sy = HallPetch::yield_strength(sigma_0, k, d0);
514 let d_back = HallPetch::grain_size_from_yield(sy, sigma_0, k);
515 assert!((d_back - d0).abs() < 1e-18, "d_back = {:.6e}", d_back);
516 }
517
518 #[test]
519 fn test_hall_petch_grain_size_same_yield() {
520 let d = HallPetch::grain_size_from_yield(50.0, 50.0, 0.5);
521 assert!(d.is_infinite());
522 }
523
524 #[test]
525 fn test_solid_solution_strengthening_zero() {
526 assert!((Strengthening::solid_solution_strengthening(0.0, 100.0) - 0.0).abs() < 1e-10);
527 }
528
529 #[test]
530 fn test_solid_solution_strengthening_positive() {
531 let ds = Strengthening::solid_solution_strengthening(0.01, 500.0);
532 assert!(ds > 0.0);
533 }
534
535 #[test]
536 fn test_precipitation_hardening_positive() {
537 let ds = Strengthening::precipitation_hardening(1e-7, 26e3, 2.86e-10);
538 assert!(ds > 0.0);
539 }
540
541 #[test]
542 fn test_work_hardening_hollomon() {
543 let expected = 500.0 * 0.2_f64.powf(0.2);
545 let result = Strengthening::work_hardening(0.2, 500.0, 0.2);
546 assert!((result - expected).abs() < 1e-9);
547 }
548
549 #[test]
550 fn test_combined_strengthening() {
551 let total = Strengthening::combined_strengthening(50.0, 30.0, 20.0, 10.0);
552 assert!((total - 110.0).abs() < 1e-10);
553 }
554
555 #[test]
556 fn test_phase_diagram_liquid() {
557 let phase = PhaseDiagram::phase_at_composition(0.3, 1500.0, (0.5, 800.0), [1200.0, 1400.0]);
559 assert_eq!(phase, BinaryPhase::Liquid);
560 }
561
562 #[test]
563 fn test_phase_diagram_alpha() {
564 let phase = PhaseDiagram::phase_at_composition(0.1, 900.0, (0.5, 600.0), [1200.0, 1100.0]);
565 assert_eq!(phase, BinaryPhase::Alpha);
566 }
567
568 #[test]
569 fn test_phase_diagram_beta() {
570 let phase = PhaseDiagram::phase_at_composition(0.8, 900.0, (0.5, 600.0), [1200.0, 1100.0]);
571 assert_eq!(phase, BinaryPhase::Beta);
572 }
573
574 #[test]
575 fn test_phase_diagram_two_phase() {
576 let phase = PhaseDiagram::phase_at_composition(0.3, 500.0, (0.5, 600.0), [1200.0, 1100.0]);
577 assert_eq!(phase, BinaryPhase::AlphaPlusBeta);
578 }
579
580 #[test]
581 fn test_alloy_thermal_conductivity_pure_components() {
582 let k = alloy_thermal_conductivity(15.0, 400.0, 0.0);
583 assert!((k - 15.0).abs() < 1e-10);
584 let k2 = alloy_thermal_conductivity(15.0, 400.0, 1.0);
585 assert!((k2 - 400.0).abs() < 1e-10);
586 }
587
588 #[test]
589 fn test_alloy_thermal_conductivity_midpoint() {
590 let k = alloy_thermal_conductivity(10.0, 20.0, 0.5);
591 assert!((k - 15.0).abs() < 1e-10);
592 }
593
594 #[test]
595 fn test_alloy_specific_heat_single() {
596 let cp = alloy_specific_heat(&[(1.0, 500.0)]);
597 assert!((cp - 500.0).abs() < 1e-10);
598 }
599
600 #[test]
601 fn test_alloy_specific_heat_mixture() {
602 let cp = alloy_specific_heat(&[(0.7, 500.0), (0.3, 900.0)]);
603 assert!((cp - 620.0).abs() < 1e-10);
604 }
605
606 #[test]
607 fn test_pren_calculation() {
608 let pren = pitting_resistance_equivalent(17.0, 2.5, 0.03);
610 let expected = 17.0 + 3.3 * 2.5 + 16.0 * 0.03;
611 assert!((pren - expected).abs() < 1e-10);
612 }
613
614 #[test]
615 fn test_galvanic_corrosion_negligible() {
616 assert_eq!(galvanic_corrosion_risk(0.0, 0.05), "negligible");
617 }
618
619 #[test]
620 fn test_galvanic_corrosion_high() {
621 assert_eq!(galvanic_corrosion_risk(0.0, 1.0), "high");
622 }
623
624 #[test]
625 fn test_galvanic_corrosion_low() {
626 assert_eq!(galvanic_corrosion_risk(0.1, 0.3), "low");
627 }
628
629 #[test]
630 fn test_aluminum_t6_6xxx() {
631 let props = AluminumAlloyT6::properties(AlloySeries::Series6xxx);
632 assert!((props.yield_strength - 276.0).abs() < 1e-6);
633 }
634
635 #[test]
636 fn test_aluminum_t6_7xxx_high_strength() {
637 let p7 = AluminumAlloyT6::properties(AlloySeries::Series7xxx);
638 let p6 = AluminumAlloyT6::properties(AlloySeries::Series6xxx);
639 assert!(p7.yield_strength > p6.yield_strength);
640 }
641
642 #[test]
643 fn test_nickel_creep_rate_positive() {
644 let rate = NickelSuperalloy::creep_rate(200.0, 1073.0, 1e-15, 4.0, 290_000.0);
645 assert!(rate > 0.0);
646 }
647
648 #[test]
649 fn test_nickel_creep_rate_increases_with_stress() {
650 let r1 = NickelSuperalloy::creep_rate(100.0, 1073.0, 1e-15, 4.0, 290_000.0);
651 let r2 = NickelSuperalloy::creep_rate(200.0, 1073.0, 1e-15, 4.0, 290_000.0);
652 assert!(r2 > r1);
653 }
654
655 #[test]
656 fn test_oxidation_mass_gain_zero_time() {
657 let dm = NickelSuperalloy::oxidation_mass_gain(1e-12, 0.0);
658 assert!((dm - 0.0).abs() < 1e-20);
659 }
660
661 #[test]
662 fn test_oxidation_mass_gain_positive() {
663 let dm = NickelSuperalloy::oxidation_mass_gain(1e-12, 3600.0);
664 assert!(dm > 0.0);
665 }
666
667 #[test]
668 fn test_carbon_equivalent_low_alloy() {
669 let ce = WeldabilityIndex::carbon_equivalent_iiw(0.15, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0);
670 assert!((ce - 0.3167).abs() < 0.001, "ce = {:.6}", ce);
671 }
672
673 #[test]
674 fn test_weldability_class_excellent() {
675 assert_eq!(WeldabilityIndex::weldability_class(0.30), "excellent");
676 }
677
678 #[test]
679 fn test_weldability_class_poor() {
680 assert_eq!(WeldabilityIndex::weldability_class(0.65), "poor");
681 }
682
683 #[test]
684 fn test_alloy_series_debug() {
685 let s = format!("{:?}", AlloySeries::Inconel718);
686 assert!(s.contains("Inconel718"));
687 }
688
689 #[test]
690 fn test_binary_phase_debug() {
691 let s = format!("{:?}", BinaryPhase::AlphaPlusBeta);
692 assert!(s.contains("Alpha"));
693 }
694}