1#![allow(dead_code)]
13#![allow(clippy::too_many_arguments)]
14
15use std::f64::consts::PI;
16
17const R_GAS: f64 = 8.314;
19
20#[derive(Debug, Clone)]
26pub struct CeramicMaterial {
27 pub youngs_modulus: f64,
29 pub poissons_ratio: f64,
31 pub fracture_toughness_kic: f64,
33 pub hardness_vickers: f64,
35 pub thermal_conductivity: f64,
37 pub thermal_expansion: f64,
39 pub melting_point: f64,
41 pub grain_size_um: f64,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum CeramicType {
52 Alumina,
54 ZirconiaYSZ,
56 SiliconCarbide,
58 SiliconNitride,
60 BoronCarbide,
62 HydroxyApatite,
64}
65
66pub fn ceramic_preset(ct: CeramicType) -> CeramicMaterial {
75 match ct {
76 CeramicType::Alumina => CeramicMaterial {
77 youngs_modulus: 380.0e9,
78 poissons_ratio: 0.22,
79 fracture_toughness_kic: 3.5e6,
80 hardness_vickers: 1600.0,
81 thermal_conductivity: 25.0,
82 thermal_expansion: 8.1e-6,
83 melting_point: 2323.0,
84 grain_size_um: 3.0,
85 },
86 CeramicType::ZirconiaYSZ => CeramicMaterial {
87 youngs_modulus: 210.0e9,
88 poissons_ratio: 0.31,
89 fracture_toughness_kic: 8.0e6,
90 hardness_vickers: 1200.0,
91 thermal_conductivity: 2.2,
92 thermal_expansion: 10.5e-6,
93 melting_point: 2988.0,
94 grain_size_um: 0.5,
95 },
96 CeramicType::SiliconCarbide => CeramicMaterial {
97 youngs_modulus: 410.0e9,
98 poissons_ratio: 0.14,
99 fracture_toughness_kic: 3.0e6,
100 hardness_vickers: 2500.0,
101 thermal_conductivity: 120.0,
102 thermal_expansion: 4.5e-6,
103 melting_point: 3003.0,
104 grain_size_um: 5.0,
105 },
106 CeramicType::SiliconNitride => CeramicMaterial {
107 youngs_modulus: 310.0e9,
108 poissons_ratio: 0.27,
109 fracture_toughness_kic: 7.0e6,
110 hardness_vickers: 1600.0,
111 thermal_conductivity: 30.0,
112 thermal_expansion: 3.2e-6,
113 melting_point: 2173.0,
114 grain_size_um: 1.0,
115 },
116 CeramicType::BoronCarbide => CeramicMaterial {
117 youngs_modulus: 460.0e9,
118 poissons_ratio: 0.17,
119 fracture_toughness_kic: 2.5e6,
120 hardness_vickers: 3000.0,
121 thermal_conductivity: 35.0,
122 thermal_expansion: 5.0e-6,
123 melting_point: 2618.0,
124 grain_size_um: 10.0,
125 },
126 CeramicType::HydroxyApatite => CeramicMaterial {
127 youngs_modulus: 80.0e9,
128 poissons_ratio: 0.27,
129 fracture_toughness_kic: 0.9e6,
130 hardness_vickers: 500.0,
131 thermal_conductivity: 1.3,
132 thermal_expansion: 14.0e-6,
133 melting_point: 1943.0,
134 grain_size_um: 2.0,
135 },
136 }
137}
138
139pub fn hall_petch_strength(k_hp: f64, sigma_0: f64, grain_size: f64) -> f64 {
152 sigma_0 + k_hp / grain_size.sqrt()
153}
154
155pub fn critical_flaw_size(kic: f64, stress: f64, geometry_factor: f64) -> f64 {
170 let ratio = kic / (geometry_factor * stress);
171 ratio * ratio / PI
172}
173
174pub fn thermal_shock_resistance(ceramic: &CeramicMaterial, delta_t: f64) -> f64 {
190 let grain_m = ceramic.grain_size_um * 1e-6 / 2.0;
191 let sigma_f = ceramic.fracture_toughness_kic / (PI * grain_m).sqrt();
192 let r = sigma_f * (1.0 - ceramic.poissons_ratio)
193 / (ceramic.youngs_modulus * ceramic.thermal_expansion);
194 r * delta_t
195}
196
197#[derive(Debug, Clone)]
205pub struct HasselmanParameters {
206 pub r_first: f64,
208 pub r_second: f64,
210 pub r_third: f64,
212 pub r_fourth: f64,
214}
215
216pub fn hasselman_parameters(
229 strength: f64,
230 youngs_modulus: f64,
231 poissons_ratio: f64,
232 cte: f64,
233 thermal_conductivity: f64,
234 fracture_toughness: f64,
235 surface_energy: f64,
236) -> HasselmanParameters {
237 let r_first = strength * (1.0 - poissons_ratio) / (youngs_modulus * cte);
239 let r_second = thermal_conductivity * r_first;
241 let r_third = youngs_modulus
243 / (fracture_toughness * fracture_toughness * (1.0 - poissons_ratio * poissons_ratio));
244 let r_fourth = youngs_modulus * surface_energy
246 / (strength * strength * (1.0 - poissons_ratio * poissons_ratio));
247 HasselmanParameters {
248 r_first,
249 r_second,
250 r_third,
251 r_fourth,
252 }
253}
254
255pub fn creep_rate_ceramic(temp: f64, stress: f64, grain_size: f64, activation_energy: f64) -> f64 {
269 let exponent = -activation_energy / (R_GAS * temp);
270 let a = 1e-20_f64;
271 a * stress * exponent.exp() / (grain_size * grain_size)
272}
273
274pub fn sintering_densification(temp: f64, time: f64, initial_density: f64) -> f64 {
287 const Q: f64 = 300_000.0;
288 let tau = (-Q / (R_GAS * temp)).exp();
289 let rho_inf = 1.0_f64;
290 let rho = rho_inf - (rho_inf - initial_density) * (-time * tau).exp();
291 rho.min(1.0)
292}
293
294pub fn grain_growth_size(
310 d0: f64,
311 exponent_n: f64,
312 prefactor_k: f64,
313 activation_energy: f64,
314 temp: f64,
315 time: f64,
316) -> f64 {
317 let d0n = d0.powf(exponent_n);
318 let kt = prefactor_k * time * (-activation_energy / (R_GAS * temp)).exp();
319 (d0n + kt).powf(1.0 / exponent_n)
320}
321
322pub fn grain_growth_rate(
333 d: f64,
334 exponent_n: f64,
335 prefactor_k: f64,
336 activation_energy: f64,
337 temp: f64,
338) -> f64 {
339 let k_eff = prefactor_k * (-activation_energy / (R_GAS * temp)).exp();
340 k_eff / (exponent_n * d.powf(exponent_n - 1.0))
341}
342
343pub fn vickers_hardness_to_mpa(hv: f64) -> f64 {
351 hv * 9.807
352}
353
354pub fn ceramic_fracture_probability(stress: f64, modulus_of_rupture: f64, weibull_m: f64) -> f64 {
369 let ratio = stress / modulus_of_rupture;
370 1.0 - (-(ratio.powf(weibull_m))).exp()
371}
372
373pub fn weibull_strength_at_probability(
384 prob_failure: f64,
385 modulus_of_rupture: f64,
386 weibull_m: f64,
387) -> f64 {
388 let ln_term = -(1.0 - prob_failure).ln();
389 modulus_of_rupture * ln_term.powf(1.0 / weibull_m)
390}
391
392pub fn weibull_volume_scaling(sigma_1: f64, v1: f64, v2: f64, weibull_m: f64) -> f64 {
404 sigma_1 * (v1 / v2).powf(1.0 / weibull_m)
405}
406
407pub fn debye_thermal_conductivity(
422 heat_capacity_vol: f64,
423 sound_velocity: f64,
424 mean_free_path: f64,
425) -> f64 {
426 heat_capacity_vol * sound_velocity * mean_free_path / 3.0
427}
428
429pub fn klemens_point_defect_scattering(scatter_param: f64, omega: f64, sound_velocity: f64) -> f64 {
443 scatter_param * omega.powi(4) / sound_velocity
444}
445
446pub fn porous_thermal_conductivity(k_solid: f64, porosity: f64) -> f64 {
456 k_solid * (1.0 - porosity) / (1.0 + porosity / 2.0)
457}
458
459pub fn umklapp_conductivity(k_ref: f64, t_ref: f64, t: f64) -> f64 {
472 k_ref * t_ref / t
473}
474
475#[derive(Debug, Clone)]
481pub struct PiezoelectricCeramic {
482 pub d33: f64,
484 pub d31: f64,
486 pub g33: f64,
488 pub epsilon_r: f64,
490 pub youngs_modulus: f64,
492 pub k33: f64,
494 pub curie_temperature: f64,
496 pub quality_factor: f64,
498}
499
500const EPS_0: f64 = 8.854_187_817e-12;
502
503pub fn pzt4_preset() -> PiezoelectricCeramic {
505 PiezoelectricCeramic {
506 d33: 289.0e-12,
507 d31: -123.0e-12,
508 g33: 26.1e-3,
509 epsilon_r: 1300.0,
510 youngs_modulus: 66.0e9,
511 k33: 0.70,
512 curie_temperature: 601.0,
513 quality_factor: 500.0,
514 }
515}
516
517pub fn pzt5a_preset() -> PiezoelectricCeramic {
519 PiezoelectricCeramic {
520 d33: 374.0e-12,
521 d31: -171.0e-12,
522 g33: 24.8e-3,
523 epsilon_r: 1700.0,
524 youngs_modulus: 52.0e9,
525 k33: 0.72,
526 curie_temperature: 638.0,
527 quality_factor: 75.0,
528 }
529}
530
531pub fn pzt5h_preset() -> PiezoelectricCeramic {
533 PiezoelectricCeramic {
534 d33: 593.0e-12,
535 d31: -274.0e-12,
536 g33: 19.7e-3,
537 epsilon_r: 3400.0,
538 youngs_modulus: 50.0e9,
539 k33: 0.75,
540 curie_temperature: 466.0,
541 quality_factor: 65.0,
542 }
543}
544
545pub fn piezo_strain_33(pzt: &PiezoelectricCeramic, e_field: f64) -> f64 {
555 pzt.d33 * e_field
556}
557
558pub fn piezo_strain_31(pzt: &PiezoelectricCeramic, e_field: f64) -> f64 {
568 pzt.d31 * e_field
569}
570
571pub fn piezo_open_circuit_voltage(pzt: &PiezoelectricCeramic, stress: f64, thickness: f64) -> f64 {
582 pzt.g33 * stress * thickness
583}
584
585pub fn piezo_charge_generated(pzt: &PiezoelectricCeramic, stress: f64, area: f64) -> f64 {
596 pzt.d33 * stress * area
597}
598
599pub fn piezo_energy_density(pzt: &PiezoelectricCeramic, e_field: f64) -> f64 {
609 0.5 * EPS_0 * pzt.epsilon_r * e_field * e_field
610}
611
612pub fn piezo_resonant_frequency(sound_velocity: f64, thickness: f64) -> f64 {
625 sound_velocity / (2.0 * thickness)
626}
627
628#[derive(Debug, Clone)]
640pub struct FerroelectricHysteresis {
641 pub saturation_polarisation: f64,
643 pub coercive_field: f64,
645 pub shape_param: f64,
647 pub remanent_polarisation: f64,
649}
650
651impl FerroelectricHysteresis {
652 pub fn new(
654 saturation_polarisation: f64,
655 coercive_field: f64,
656 shape_param: f64,
657 remanent_polarisation: f64,
658 ) -> Self {
659 Self {
660 saturation_polarisation,
661 coercive_field,
662 shape_param,
663 remanent_polarisation,
664 }
665 }
666
667 pub fn barium_titanate() -> Self {
669 Self::new(0.26, 1.0e5, 5.0e4, 0.22)
670 }
671
672 pub fn pzt_soft() -> Self {
674 Self::new(0.35, 1.4e5, 6.0e4, 0.30)
675 }
676
677 pub fn polarisation_upper(&self, e_field: f64) -> f64 {
686 self.saturation_polarisation * ((e_field + self.coercive_field) / self.shape_param).tanh()
687 }
688
689 pub fn polarisation_lower(&self, e_field: f64) -> f64 {
698 self.saturation_polarisation * ((e_field - self.coercive_field) / self.shape_param).tanh()
699 }
700
701 pub fn hysteresis_loss_per_cycle(&self) -> f64 {
708 4.0 * self.coercive_field * self.remanent_polarisation
709 }
710
711 pub fn curie_weiss_permittivity(curie_constant: f64, curie_temp: f64, temp: f64) -> f64 {
722 if temp <= curie_temp {
723 f64::INFINITY
724 } else {
725 curie_constant / (temp - curie_temp)
726 }
727 }
728}
729
730#[derive(Debug, Clone)]
738pub struct ZirconiaToughening {
739 pub transformation_strain: f64,
741 pub volume_fraction: f64,
743 pub shear_modulus: f64,
745 pub base_toughness: f64,
747 pub zone_height: f64,
749}
750
751impl ZirconiaToughening {
752 pub fn new(
754 transformation_strain: f64,
755 volume_fraction: f64,
756 shear_modulus: f64,
757 base_toughness: f64,
758 zone_height: f64,
759 ) -> Self {
760 Self {
761 transformation_strain,
762 volume_fraction,
763 shear_modulus,
764 base_toughness,
765 zone_height,
766 }
767 }
768
769 pub fn y_tzp_3mol() -> Self {
771 Self::new(0.04, 0.30, 80.0e9, 5.0e6, 50.0e-6)
772 }
773
774 pub fn toughening_increment(&self) -> f64 {
780 let e_eff = 2.0 * self.shear_modulus;
781 0.22 * e_eff * self.transformation_strain * self.volume_fraction * self.zone_height.sqrt()
782 }
783
784 pub fn effective_toughness(&self) -> f64 {
786 self.base_toughness + self.toughening_increment()
787 }
788
789 pub fn transformation_stress(&self) -> f64 {
791 self.shear_modulus * self.transformation_strain / (1.0 - self.volume_fraction)
792 }
793
794 pub fn zone_size_estimate(&self) -> f64 {
798 let sigma_t = self.transformation_stress();
799 let kic = self.effective_toughness();
800 (kic / sigma_t).powi(2) / (3.0 * PI)
801 }
802
803 pub fn critical_grain_size(
815 &self,
816 surface_energy: f64,
817 chem_driving_force: f64,
818 youngs_modulus: f64,
819 poissons_ratio: f64,
820 ) -> f64 {
821 let strain_energy =
822 youngs_modulus * self.transformation_strain.powi(2) / (1.0 - poissons_ratio);
823 6.0 * surface_energy / (chem_driving_force + strain_energy)
824 }
825}
826
827pub fn alumina_modulus_vs_temp(e0: f64, temp: f64, t_ref: f64, b_coeff: f64) -> f64 {
845 (e0 * (1.0 - b_coeff * (temp - t_ref))).max(0.0)
846}
847
848pub fn alumina_size_effect(sigma_ref: f64, v_ref: f64, v_target: f64, weibull_m: f64) -> f64 {
860 weibull_volume_scaling(sigma_ref, v_ref, v_target, weibull_m)
861}
862
863pub fn alumina_thermal_conductivity_vs_temp(a_coeff: f64, b_coeff: f64, temp: f64) -> f64 {
874 a_coeff / temp + b_coeff
875}
876
877pub fn indentation_fracture_toughness(
893 youngs_modulus: f64,
894 hardness: f64,
895 load: f64,
896 crack_length: f64,
897) -> f64 {
898 0.016 * (youngs_modulus / hardness).sqrt() * load / crack_length.powf(1.5)
899}
900
901pub fn subcritical_crack_growth_rate(k_i: f64, k_ic: f64, paris_a: f64, paris_n: f64) -> f64 {
919 paris_a * (k_i / k_ic).powf(paris_n)
920}
921
922pub fn cycles_to_failure(
937 a0: f64,
938 stress: f64,
939 geometry_factor: f64,
940 k_ic: f64,
941 paris_a: f64,
942 paris_n: f64,
943 steps: usize,
944) -> f64 {
945 let a_c = critical_flaw_size(k_ic, stress, geometry_factor);
946 if a0 >= a_c {
947 return 0.0;
948 }
949 let da = (a_c - a0) / steps as f64;
950 let mut cycles = 0.0;
951 let mut a = a0;
952 for _ in 0..steps {
953 let k_i = geometry_factor * stress * (PI * a).sqrt();
954 let rate = subcritical_crack_growth_rate(k_i, k_ic, paris_a, paris_n);
955 if rate < 1e-30 {
956 cycles += da / 1e-30;
957 } else {
958 cycles += da / rate;
959 }
960 a += da;
961 }
962 cycles
963}
964
965pub fn r_curve_toughness(da: f64, k_init: f64, k_steady: f64, lambda: f64) -> f64 {
984 k_init + (k_steady - k_init) * (1.0 - (-da / lambda).exp())
985}
986
987#[cfg(test)]
992mod tests {
993 use super::*;
994
995 const EPS: f64 = 1e-10;
996
997 #[test]
1000 fn test_preset_alumina_modulus() {
1001 let m = ceramic_preset(CeramicType::Alumina);
1002 assert!((m.youngs_modulus - 380.0e9).abs() < 1.0, "Alumina E");
1003 }
1004
1005 #[test]
1006 fn test_preset_zirconia_toughness() {
1007 let m = ceramic_preset(CeramicType::ZirconiaYSZ);
1008 assert!(m.fracture_toughness_kic > 5.0e6, "YSZ KIC > 5 MPa sqrt(m)");
1009 }
1010
1011 #[test]
1012 fn test_preset_sic_hardness() {
1013 let m = ceramic_preset(CeramicType::SiliconCarbide);
1014 assert!(m.hardness_vickers > 2000.0, "SiC HV > 2000");
1015 }
1016
1017 #[test]
1018 fn test_preset_si3n4_conductivity() {
1019 let m = ceramic_preset(CeramicType::SiliconNitride);
1020 assert!(m.thermal_conductivity > 20.0, "Si3N4 k > 20 W/mK");
1021 }
1022
1023 #[test]
1024 fn test_preset_b4c_melting_point() {
1025 let m = ceramic_preset(CeramicType::BoronCarbide);
1026 assert!(m.melting_point > 2500.0, "B4C melting > 2500 K");
1027 }
1028
1029 #[test]
1030 fn test_preset_ha_modulus_low() {
1031 let m = ceramic_preset(CeramicType::HydroxyApatite);
1032 assert!(m.youngs_modulus < 120.0e9, "HA E < 120 GPa");
1033 }
1034
1035 #[test]
1036 fn test_preset_all_positive_fields() {
1037 for ct in [
1038 CeramicType::Alumina,
1039 CeramicType::ZirconiaYSZ,
1040 CeramicType::SiliconCarbide,
1041 CeramicType::SiliconNitride,
1042 CeramicType::BoronCarbide,
1043 CeramicType::HydroxyApatite,
1044 ] {
1045 let m = ceramic_preset(ct);
1046 assert!(m.youngs_modulus > 0.0);
1047 assert!(m.fracture_toughness_kic > 0.0);
1048 assert!(m.hardness_vickers > 0.0);
1049 assert!(m.thermal_conductivity > 0.0);
1050 assert!(m.grain_size_um > 0.0);
1051 }
1052 }
1053
1054 #[test]
1057 fn test_hall_petch_basic() {
1058 let s = hall_petch_strength(0.5e6, 100.0e6, 1e-6);
1059 assert!((s - (100.0e6 + 0.5e6 / 1e-3)).abs() < 1.0, "HP value");
1060 }
1061
1062 #[test]
1063 fn test_hall_petch_larger_grain_lower_strength() {
1064 let s_fine = hall_petch_strength(0.5e6, 100.0e6, 1e-6);
1065 let s_coarse = hall_petch_strength(0.5e6, 100.0e6, 100e-6);
1066 assert!(s_fine > s_coarse, "finer grain -> higher strength");
1067 }
1068
1069 #[test]
1070 fn test_hall_petch_zero_k() {
1071 let s = hall_petch_strength(0.0, 200.0e6, 5e-6);
1072 assert!((s - 200.0e6).abs() < EPS, "k=0 -> sigma_0");
1073 }
1074
1075 #[test]
1078 fn test_critical_flaw_size_positive() {
1079 let a = critical_flaw_size(3.5e6, 200.0e6, 1.0);
1080 assert!(a > 0.0, "flaw size must be positive");
1081 }
1082
1083 #[test]
1084 fn test_critical_flaw_size_decreases_with_stress() {
1085 let a1 = critical_flaw_size(3.5e6, 100.0e6, 1.0);
1086 let a2 = critical_flaw_size(3.5e6, 200.0e6, 1.0);
1087 assert!(a1 > a2, "higher stress -> smaller critical flaw");
1088 }
1089
1090 #[test]
1091 fn test_critical_flaw_size_formula() {
1092 let kic = 3.5e6_f64;
1093 let sig = 200.0e6_f64;
1094 let y = 1.12_f64;
1095 let expected = (kic / (y * sig)).powi(2) / PI;
1096 let got = critical_flaw_size(kic, sig, y);
1097 assert!((got - expected).abs() < 1e-20, "formula");
1098 }
1099
1100 #[test]
1103 fn test_tsr_positive() {
1104 let m = ceramic_preset(CeramicType::Alumina);
1105 let r = thermal_shock_resistance(&m, 200.0);
1106 assert!(r > 0.0, "TSR must be positive");
1107 }
1108
1109 #[test]
1110 fn test_tsr_scales_with_delta_t() {
1111 let m = ceramic_preset(CeramicType::SiliconCarbide);
1112 let r1 = thermal_shock_resistance(&m, 100.0);
1113 let r2 = thermal_shock_resistance(&m, 200.0);
1114 assert!((r2 - 2.0 * r1).abs() < EPS * 1e6, "TSR linear in delta_T");
1115 }
1116
1117 #[test]
1120 fn test_hasselman_r_first_positive() {
1121 let h = hasselman_parameters(400.0e6, 380.0e9, 0.22, 8.1e-6, 25.0, 3.5e6, 1.0);
1122 assert!(h.r_first > 0.0, "R must be positive");
1123 }
1124
1125 #[test]
1126 fn test_hasselman_r_second_equals_k_times_r() {
1127 let k = 25.0;
1128 let h = hasselman_parameters(400.0e6, 380.0e9, 0.22, 8.1e-6, k, 3.5e6, 1.0);
1129 assert!((h.r_second - k * h.r_first).abs() < 1e-6, "R' = k * R");
1130 }
1131
1132 #[test]
1133 fn test_hasselman_r_fourth_positive() {
1134 let h = hasselman_parameters(400.0e6, 380.0e9, 0.22, 8.1e-6, 25.0, 3.5e6, 1.0);
1135 assert!(h.r_fourth > 0.0, "R'''' must be positive");
1136 }
1137
1138 #[test]
1141 fn test_creep_rate_positive() {
1142 let rate = creep_rate_ceramic(1500.0, 100.0e6, 5e-6, 400_000.0);
1143 assert!(rate > 0.0, "creep rate must be positive");
1144 }
1145
1146 #[test]
1147 fn test_creep_rate_higher_temp_faster() {
1148 let r1 = creep_rate_ceramic(1200.0, 100.0e6, 5e-6, 300_000.0);
1149 let r2 = creep_rate_ceramic(1600.0, 100.0e6, 5e-6, 300_000.0);
1150 assert!(r2 > r1, "higher T -> faster creep");
1151 }
1152
1153 #[test]
1156 fn test_sintering_density_increases_with_time() {
1157 let rho0 = 0.60;
1158 let d1 = sintering_densification(1600.0, 100.0, rho0);
1159 let d2 = sintering_densification(1600.0, 3600.0, rho0);
1160 assert!(d2 >= d1, "density should increase with time");
1161 }
1162
1163 #[test]
1164 fn test_sintering_density_bounded() {
1165 let d = sintering_densification(1800.0, 1e9, 0.50);
1166 assert!(d <= 1.0, "density cannot exceed 1.0");
1167 assert!(d >= 0.50, "density cannot decrease from initial");
1168 }
1169
1170 #[test]
1171 fn test_sintering_zero_time() {
1172 let rho0 = 0.65;
1173 let d = sintering_densification(1600.0, 0.0, rho0);
1174 assert!((d - rho0).abs() < 1e-12, "no sintering at t=0");
1175 }
1176
1177 #[test]
1180 fn test_grain_growth_increases_with_time() {
1181 let d1 = grain_growth_size(1e-6, 2.0, 1e-14, 300e3, 1500.0, 100.0);
1182 let d2 = grain_growth_size(1e-6, 2.0, 1e-14, 300e3, 1500.0, 10000.0);
1183 assert!(d2 > d1, "grain size should increase with time");
1184 }
1185
1186 #[test]
1187 fn test_grain_growth_zero_time_equals_initial() {
1188 let d0 = 1.5e-6;
1189 let d = grain_growth_size(d0, 2.0, 1e-14, 300e3, 1500.0, 0.0);
1190 assert!((d - d0).abs() < 1e-15, "d(t=0) = d0");
1191 }
1192
1193 #[test]
1194 fn test_grain_growth_rate_positive() {
1195 let rate = grain_growth_rate(2e-6, 2.0, 1e-14, 300e3, 1500.0);
1196 assert!(rate > 0.0, "growth rate must be positive");
1197 }
1198
1199 #[test]
1202 fn test_hv_conversion_known_value() {
1203 let mpa = vickers_hardness_to_mpa(1000.0);
1204 assert!((mpa - 9807.0).abs() < 0.01, "1000 HV = 9807 MPa");
1205 }
1206
1207 #[test]
1210 fn test_weibull_at_scale_param() {
1211 let p = ceramic_fracture_probability(100.0e6, 100.0e6, 10.0);
1212 let expected = 1.0 - (-1.0_f64).exp();
1213 assert!((p - expected).abs() < 1e-12, "P_f at scale param");
1214 }
1215
1216 #[test]
1217 fn test_weibull_zero_stress() {
1218 let p = ceramic_fracture_probability(0.0, 100.0e6, 10.0);
1219 assert!(p.abs() < EPS, "zero stress -> zero probability");
1220 }
1221
1222 #[test]
1223 fn test_weibull_monotone_increasing() {
1224 let p1 = ceramic_fracture_probability(80.0e6, 100.0e6, 10.0);
1225 let p2 = ceramic_fracture_probability(120.0e6, 100.0e6, 10.0);
1226 assert!(p2 > p1, "P_f increases with stress");
1227 }
1228
1229 #[test]
1232 fn test_weibull_strength_at_50_percent() {
1233 let sigma_0 = 400.0e6;
1234 let m = 10.0;
1235 let sigma = weibull_strength_at_probability(0.5, sigma_0, m);
1236 let pf = ceramic_fracture_probability(sigma, sigma_0, m);
1238 assert!((pf - 0.5).abs() < 1e-10, "round-trip P_f = 0.5");
1239 }
1240
1241 #[test]
1244 fn test_volume_scaling_larger_weaker() {
1245 let sigma = weibull_volume_scaling(400.0e6, 1e-6, 1e-3, 10.0);
1246 assert!(sigma < 400.0e6, "larger volume -> lower strength");
1247 }
1248
1249 #[test]
1250 fn test_volume_scaling_same_volume() {
1251 let sigma = weibull_volume_scaling(400.0e6, 1e-6, 1e-6, 10.0);
1252 assert!(
1253 (sigma - 400.0e6).abs() < EPS,
1254 "same volume -> same strength"
1255 );
1256 }
1257
1258 #[test]
1261 fn test_debye_conductivity_positive() {
1262 let k = debye_thermal_conductivity(2.5e6, 5000.0, 10e-9);
1263 assert!(k > 0.0, "conductivity must be positive");
1264 }
1265
1266 #[test]
1267 fn test_debye_conductivity_proportional_to_mfp() {
1268 let k1 = debye_thermal_conductivity(2.5e6, 5000.0, 10e-9);
1269 let k2 = debye_thermal_conductivity(2.5e6, 5000.0, 20e-9);
1270 assert!((k2 / k1 - 2.0).abs() < 1e-10, "linear in mfp");
1271 }
1272
1273 #[test]
1274 fn test_porous_conductivity_decreases_with_porosity() {
1275 let k0 = porous_thermal_conductivity(25.0, 0.0);
1276 let k1 = porous_thermal_conductivity(25.0, 0.3);
1277 assert!(k1 < k0, "porosity reduces conductivity");
1278 }
1279
1280 #[test]
1281 fn test_porous_conductivity_zero_porosity_equals_solid() {
1282 let k = porous_thermal_conductivity(25.0, 0.0);
1283 assert!((k - 25.0).abs() < 1e-10, "zero porosity -> solid value");
1284 }
1285
1286 #[test]
1287 fn test_umklapp_conductivity_inverse_t() {
1288 let k1 = umklapp_conductivity(25.0, 300.0, 300.0);
1289 let k2 = umklapp_conductivity(25.0, 300.0, 600.0);
1290 assert!((k1 - 25.0).abs() < 1e-10, "at T_ref -> k_ref");
1291 assert!((k2 - 12.5).abs() < 1e-10, "at 2*T_ref -> k_ref/2");
1292 }
1293
1294 #[test]
1297 fn test_pzt4_d33_positive() {
1298 let pzt = pzt4_preset();
1299 assert!(pzt.d33 > 0.0, "d33 must be positive for PZT");
1300 }
1301
1302 #[test]
1303 fn test_pzt5h_higher_d33_than_pzt4() {
1304 let p4 = pzt4_preset();
1305 let p5h = pzt5h_preset();
1306 assert!(p5h.d33 > p4.d33, "PZT-5H is softer -> higher d33");
1307 }
1308
1309 #[test]
1310 fn test_piezo_strain_sign() {
1311 let pzt = pzt5a_preset();
1312 let s33 = piezo_strain_33(&pzt, 1e6);
1313 let s31 = piezo_strain_31(&pzt, 1e6);
1314 assert!(s33 > 0.0, "d33 > 0 -> positive axial strain");
1315 assert!(s31 < 0.0, "d31 < 0 -> negative transverse strain");
1316 }
1317
1318 #[test]
1319 fn test_piezo_voltage_proportional_to_stress() {
1320 let pzt = pzt4_preset();
1321 let v1 = piezo_open_circuit_voltage(&pzt, 1.0e6, 1e-3);
1322 let v2 = piezo_open_circuit_voltage(&pzt, 2.0e6, 1e-3);
1323 assert!((v2 / v1 - 2.0).abs() < 1e-10, "V linear in stress");
1324 }
1325
1326 #[test]
1327 fn test_piezo_charge_proportional_to_area() {
1328 let pzt = pzt4_preset();
1329 let q1 = piezo_charge_generated(&pzt, 1e6, 1e-4);
1330 let q2 = piezo_charge_generated(&pzt, 1e6, 2e-4);
1331 assert!((q2 / q1 - 2.0).abs() < 1e-10, "charge linear in area");
1332 }
1333
1334 #[test]
1335 fn test_piezo_energy_density_positive() {
1336 let pzt = pzt5a_preset();
1337 let u = piezo_energy_density(&pzt, 1e6);
1338 assert!(u > 0.0, "energy density must be positive");
1339 }
1340
1341 #[test]
1342 fn test_piezo_resonant_frequency() {
1343 let f = piezo_resonant_frequency(4000.0, 1e-3);
1344 assert!((f - 2.0e6).abs() < 1.0, "f_r = v/(2t) = 4000/0.002 = 2 MHz");
1345 }
1346
1347 #[test]
1350 fn test_hysteresis_upper_at_zero_field() {
1351 let h = FerroelectricHysteresis::barium_titanate();
1352 let p = h.polarisation_upper(0.0);
1353 assert!(p > 0.0, "upper branch at E=0 should be positive (remanent)");
1355 }
1356
1357 #[test]
1358 fn test_hysteresis_lower_at_zero_field() {
1359 let h = FerroelectricHysteresis::barium_titanate();
1360 let p = h.polarisation_lower(0.0);
1361 assert!(p < 0.0, "lower branch at E=0 should be negative");
1363 }
1364
1365 #[test]
1366 fn test_hysteresis_saturation() {
1367 let h = FerroelectricHysteresis::pzt_soft();
1368 let p_up = h.polarisation_upper(1e7);
1369 assert!(
1371 (p_up - h.saturation_polarisation).abs() < 0.01,
1372 "should saturate at high field"
1373 );
1374 }
1375
1376 #[test]
1377 fn test_hysteresis_loss_positive() {
1378 let h = FerroelectricHysteresis::barium_titanate();
1379 assert!(h.hysteresis_loss_per_cycle() > 0.0, "loss must be positive");
1380 }
1381
1382 #[test]
1383 fn test_curie_weiss_diverges_at_tc() {
1384 let eps = FerroelectricHysteresis::curie_weiss_permittivity(1.7e5, 393.0, 393.0);
1385 assert!(eps.is_infinite(), "should diverge at T_c");
1386 }
1387
1388 #[test]
1389 fn test_curie_weiss_decreases_above_tc() {
1390 let eps1 = FerroelectricHysteresis::curie_weiss_permittivity(1.7e5, 393.0, 400.0);
1391 let eps2 = FerroelectricHysteresis::curie_weiss_permittivity(1.7e5, 393.0, 500.0);
1392 assert!(
1393 eps1 > eps2,
1394 "permittivity decreases as T increases above T_c"
1395 );
1396 }
1397
1398 #[test]
1401 fn test_zirconia_toughening_increment_positive() {
1402 let z = ZirconiaToughening::y_tzp_3mol();
1403 assert!(z.toughening_increment() > 0.0, "delta_K must be positive");
1404 }
1405
1406 #[test]
1407 fn test_zirconia_effective_gt_base() {
1408 let z = ZirconiaToughening::y_tzp_3mol();
1409 assert!(z.effective_toughness() > z.base_toughness, "K_eff > K_0");
1410 }
1411
1412 #[test]
1413 fn test_zirconia_toughening_increases_with_fv() {
1414 let z1 = ZirconiaToughening::new(0.04, 0.1, 80e9, 5e6, 50e-6);
1415 let z2 = ZirconiaToughening::new(0.04, 0.4, 80e9, 5e6, 50e-6);
1416 assert!(
1417 z2.toughening_increment() > z1.toughening_increment(),
1418 "more transformable phase -> more toughening"
1419 );
1420 }
1421
1422 #[test]
1423 fn test_zirconia_zone_size_positive() {
1424 let z = ZirconiaToughening::y_tzp_3mol();
1425 assert!(z.zone_size_estimate() > 0.0, "zone size must be positive");
1426 }
1427
1428 #[test]
1429 fn test_zirconia_transformation_stress_positive() {
1430 let z = ZirconiaToughening::y_tzp_3mol();
1431 assert!(z.transformation_stress() > 0.0, "sigma_t must be positive");
1432 }
1433
1434 #[test]
1435 fn test_zirconia_critical_grain_size_positive() {
1436 let z = ZirconiaToughening::y_tzp_3mol();
1437 let dc = z.critical_grain_size(1.0, 100e6, 210e9, 0.31);
1438 assert!(dc > 0.0, "critical grain size must be positive");
1439 }
1440
1441 #[test]
1444 fn test_alumina_modulus_decreases_with_temp() {
1445 let e_rt = alumina_modulus_vs_temp(380e9, 300.0, 300.0, 5e-5);
1446 let e_ht = alumina_modulus_vs_temp(380e9, 1000.0, 300.0, 5e-5);
1447 assert!(e_ht < e_rt, "modulus should decrease at high temperature");
1448 }
1449
1450 #[test]
1451 fn test_alumina_modulus_at_ref_temp() {
1452 let e = alumina_modulus_vs_temp(380e9, 300.0, 300.0, 5e-5);
1453 assert!((e - 380e9).abs() < 1.0, "E(T_ref) = E_0");
1454 }
1455
1456 #[test]
1459 fn test_indentation_toughness_positive() {
1460 let k = indentation_fracture_toughness(380e9, 18e9, 10.0, 50e-6);
1461 assert!(k > 0.0, "K_IC must be positive");
1462 }
1463
1464 #[test]
1467 fn test_scg_rate_increases_with_ki() {
1468 let r1 = subcritical_crack_growth_rate(2e6, 4e6, 1e-15, 20.0);
1469 let r2 = subcritical_crack_growth_rate(3e6, 4e6, 1e-15, 20.0);
1470 assert!(r2 > r1, "higher K_I -> faster growth");
1471 }
1472
1473 #[test]
1474 fn test_cycles_to_failure_positive() {
1475 let n = cycles_to_failure(10e-6, 200e6, 1.12, 3.5e6, 1e-15, 20.0, 1000);
1476 assert!(n > 0.0, "cycles must be positive for subcritical crack");
1477 }
1478
1479 #[test]
1482 fn test_r_curve_at_zero_extension_equals_k0() {
1483 let k = r_curve_toughness(0.0, 3.0e6, 8.0e6, 100e-6);
1484 assert!((k - 3.0e6).abs() < 1e-6, "K_R(0) = K_0");
1485 }
1486
1487 #[test]
1488 fn test_r_curve_approaches_steady_state() {
1489 let k = r_curve_toughness(1.0, 3.0e6, 8.0e6, 100e-6);
1490 assert!((k - 8.0e6).abs() < 1.0, "K_R at large extension -> K_ss");
1491 }
1492
1493 #[test]
1494 fn test_r_curve_monotonically_increasing() {
1495 let k1 = r_curve_toughness(10e-6, 3.0e6, 8.0e6, 100e-6);
1496 let k2 = r_curve_toughness(50e-6, 3.0e6, 8.0e6, 100e-6);
1497 assert!(k2 > k1, "R-curve should be monotonically increasing");
1498 }
1499}