1#![allow(dead_code)]
11#![allow(clippy::too_many_arguments)]
12
13use std::f64::consts::PI;
14
15const R_GAS: f64 = 8.314_462_618;
17
18#[derive(Debug, Clone)]
27pub struct CeramicProperties {
28 pub young_modulus: f64,
30 pub poisson_ratio: f64,
32 pub thermal_conductivity: f64,
34 pub cte: f64,
36 pub hardness: f64,
38 pub fracture_toughness: f64,
40 pub flexural_strength: f64,
42 pub weibull_modulus: f64,
44 pub surface_energy: f64,
46 pub name: String,
48}
49
50impl CeramicProperties {
51 #[allow(clippy::too_many_arguments)]
53 pub fn new(
54 young_modulus: f64,
55 poisson_ratio: f64,
56 thermal_conductivity: f64,
57 cte: f64,
58 hardness: f64,
59 fracture_toughness: f64,
60 flexural_strength: f64,
61 weibull_modulus: f64,
62 surface_energy: f64,
63 name: impl Into<String>,
64 ) -> Self {
65 Self {
66 young_modulus,
67 poisson_ratio,
68 thermal_conductivity,
69 cte,
70 hardness,
71 fracture_toughness,
72 flexural_strength,
73 weibull_modulus,
74 surface_energy,
75 name: name.into(),
76 }
77 }
78
79 pub fn alumina() -> Self {
81 Self::new(
82 380e9, 0.22, 30.0, 8.1e-6, 18.0, 4.0, 400e6, 10.0, 1.0, "Al2O3",
83 )
84 }
85
86 pub fn silicon_carbide() -> Self {
88 Self::new(
89 420e9, 0.17, 120.0, 4.5e-6, 27.0, 4.5, 500e6, 12.0, 1.5, "SiC",
90 )
91 }
92
93 pub fn silicon_nitride() -> Self {
95 Self::new(
96 310e9, 0.27, 30.0, 3.2e-6, 16.0, 7.0, 700e6, 15.0, 2.0, "Si3N4",
97 )
98 }
99
100 pub fn zirconia() -> Self {
102 Self::new(
103 200e9, 0.31, 2.5, 10.5e-6, 12.0, 10.0, 1000e6, 8.0, 1.2, "ZrO2",
104 )
105 }
106
107 pub fn titania() -> Self {
109 Self::new(
110 230e9, 0.27, 11.8, 8.4e-6, 10.0, 2.0, 200e6, 7.0, 0.5, "TiO2",
111 )
112 }
113}
114
115#[derive(Debug, Clone)]
124pub struct BrittleFractureCeramic {
125 pub props: CeramicProperties,
127 pub geometry_factor: f64,
129}
130
131impl BrittleFractureCeramic {
132 pub fn new(props: CeramicProperties, geometry_factor: f64) -> Self {
134 Self {
135 props,
136 geometry_factor,
137 }
138 }
139
140 pub fn griffith_critical_stress(&self, crack_half_length: f64) -> f64 {
146 let e = self.props.young_modulus;
147 let gamma = self.props.surface_energy;
148 (2.0 * e * gamma / (PI * crack_half_length)).sqrt()
149 }
150
151 pub fn stress_intensity(&self, sigma: f64, crack_half_length: f64) -> f64 {
157 self.geometry_factor * sigma * (PI * crack_half_length).sqrt()
158 }
159
160 pub fn is_critical(&self, sigma: f64, crack_half_length: f64) -> bool {
162 let ki = self.stress_intensity(sigma, crack_half_length);
163 let kic = self.props.fracture_toughness * 1e6; ki >= kic
165 }
166
167 pub fn critical_crack_length(&self, sigma: f64) -> f64 {
173 let kic = self.props.fracture_toughness * 1e6;
174 let y_sigma = self.geometry_factor * sigma;
175 (kic / y_sigma).powi(2) / PI
176 }
177
178 pub fn weibull_survival_probability(&self, sigma: f64) -> f64 {
186 let sigma_0 = self.props.flexural_strength;
187 let m = self.props.weibull_modulus;
188 (-(sigma / sigma_0).powf(m)).exp()
189 }
190
191 pub fn weibull_failure_probability(&self, sigma: f64) -> f64 {
193 1.0 - self.weibull_survival_probability(sigma)
194 }
195
196 pub fn weibull_median_strength(&self) -> f64 {
200 let sigma_0 = self.props.flexural_strength;
201 let m = self.props.weibull_modulus;
202 sigma_0 * 2_f64.ln().powf(1.0 / m)
203 }
204
205 pub fn weibull_mean_strength(&self) -> f64 {
209 let sigma_0 = self.props.flexural_strength;
210 let m = self.props.weibull_modulus;
211 sigma_0 * gamma_approx(1.0 + 1.0 / m)
212 }
213}
214
215fn gamma_approx(x: f64) -> f64 {
217 let c = [
219 1.000_000_000_190_015,
220 76.180_091_729_471_46,
221 -86.505_320_329_416_77,
222 24.014_098_240_830_91,
223 -1.231_739_572_450_155,
224 1.208_650_973_866_179e-3,
225 -5.395_239_384_953_0e-6,
226 ];
227 let x = x - 1.0;
228 let mut ser = c[0];
229 for (i, &ci) in c[1..].iter().enumerate() {
230 ser += ci / (x + i as f64 + 1.0);
231 }
232 let t = x + 5.5; std::f64::consts::TAU.sqrt() * t.powf(x + 0.5) * (-t).exp() * ser
234}
235
236#[derive(Debug, Clone)]
244pub struct ThermalShockResistance {
245 pub props: CeramicProperties,
247}
248
249impl ThermalShockResistance {
250 pub fn new(props: CeramicProperties) -> Self {
252 Self { props }
253 }
254
255 pub fn r_parameter(&self) -> f64 {
261 let sigma_f = self.props.flexural_strength;
262 let nu = self.props.poisson_ratio;
263 let alpha = self.props.cte;
264 let e = self.props.young_modulus;
265 sigma_f * (1.0 - nu) / (alpha * e)
266 }
267
268 pub fn r_prime_parameter(&self) -> f64 {
272 self.props.thermal_conductivity * self.r_parameter()
273 }
274
275 pub fn critical_temperature_difference(&self) -> f64 {
279 self.r_parameter()
280 }
281
282 pub fn r_damage_parameter(&self) -> f64 {
286 let e = self.props.young_modulus;
287 let gamma = self.props.surface_energy;
288 let sigma_f = self.props.flexural_strength;
289 e * gamma / (sigma_f * sigma_f)
290 }
291}
292
293#[derive(Debug, Clone)]
301pub struct SinteringModel {
302 pub neck_growth_prefactor: f64,
304 pub neck_growth_exponent: f64,
306 pub activation_energy_neck: f64,
308 pub particle_radius: f64,
310 pub densification_prefactor: f64,
312 pub densification_grain_exponent: f64,
314 pub activation_energy_densification: f64,
316 pub initial_density: f64,
318}
319
320impl SinteringModel {
321 #[allow(clippy::too_many_arguments)]
323 pub fn new(
324 neck_growth_prefactor: f64,
325 neck_growth_exponent: f64,
326 activation_energy_neck: f64,
327 particle_radius: f64,
328 densification_prefactor: f64,
329 densification_grain_exponent: f64,
330 activation_energy_densification: f64,
331 initial_density: f64,
332 ) -> Self {
333 Self {
334 neck_growth_prefactor,
335 neck_growth_exponent,
336 activation_energy_neck,
337 particle_radius,
338 densification_prefactor,
339 densification_grain_exponent,
340 activation_energy_densification,
341 initial_density,
342 }
343 }
344
345 pub fn neck_ratio(&self, time: f64, temp: f64) -> f64 {
350 let n = self.neck_growth_exponent;
351 let rate = self.neck_growth_prefactor
352 * time
353 * (-self.activation_energy_neck / (R_GAS * temp)).exp();
354 rate.powf(1.0 / n)
355 }
356
357 pub fn densification_rate(&self, r_grain: f64, temp: f64) -> f64 {
362 let m = self.densification_grain_exponent;
363 self.densification_prefactor
364 * r_grain.powf(-m)
365 * (-self.activation_energy_densification / (R_GAS * temp)).exp()
366 }
367
368 pub fn density_at_time(&self, time: f64, r_grain: f64, temp: f64, steps: usize) -> f64 {
371 let dt = time / steps as f64;
372 let mut rho = self.initial_density;
373 let rate = self.densification_rate(r_grain, temp);
374 for _ in 0..steps {
375 rho += rate * dt;
376 if rho >= 1.0 {
377 return 1.0;
378 }
379 }
380 rho
381 }
382}
383
384#[derive(Debug, Clone)]
392pub struct GrainGrowth {
393 pub exponent: f64,
395 pub prefactor: f64,
397 pub activation_energy: f64,
399 pub initial_grain_size: f64,
401}
402
403impl GrainGrowth {
404 pub fn new(
406 exponent: f64,
407 prefactor: f64,
408 activation_energy: f64,
409 initial_grain_size: f64,
410 ) -> Self {
411 Self {
412 exponent,
413 prefactor,
414 activation_energy,
415 initial_grain_size,
416 }
417 }
418
419 pub fn grain_size(&self, time: f64, temp: f64) -> f64 {
423 let n = self.exponent;
424 let d0n = self.initial_grain_size.powf(n);
425 let kt = self.prefactor * time * (-self.activation_energy / (R_GAS * temp)).exp();
426 (d0n + kt).powf(1.0 / n)
427 }
428
429 pub fn growth_rate(&self, d: f64, temp: f64) -> f64 {
431 let n = self.exponent;
432 let k_eff = self.prefactor * (-self.activation_energy / (R_GAS * temp)).exp();
433 k_eff / (n * d.powf(n - 1.0))
434 }
435
436 pub fn is_saturated(&self, time: f64, temp: f64, tol: f64) -> bool {
439 let d = self.grain_size(time, temp);
440 let d_long = self.grain_size(time * 1e6, temp);
441 (d_long - d) / d_long < tol
442 }
443}
444
445#[derive(Debug, Clone)]
453pub struct CreepCeramic {
454 pub prefactor: f64,
456 pub stress_exponent: f64,
458 pub activation_energy: f64,
460}
461
462impl CreepCeramic {
463 pub fn new(prefactor: f64, stress_exponent: f64, activation_energy: f64) -> Self {
465 Self {
466 prefactor,
467 stress_exponent,
468 activation_energy,
469 }
470 }
471
472 pub fn creep_rate(&self, sigma: f64, temp: f64) -> f64 {
474 self.prefactor
475 * sigma.powf(self.stress_exponent)
476 * (-self.activation_energy / (R_GAS * temp)).exp()
477 }
478
479 pub fn apparent_activation_energy(rate1: f64, temp1: f64, rate2: f64, temp2: f64) -> f64 {
485 R_GAS * temp1 * temp2 / (temp2 - temp1) * (rate2 / rate1).ln()
486 }
487
488 pub fn accumulated_strain(&self, sigma: f64, temp: f64, time: f64) -> f64 {
490 self.creep_rate(sigma, temp) * time
491 }
492}
493
494#[derive(Debug, Clone)]
502pub struct DielectricCeramic {
503 pub curie_constant: f64,
505 pub curie_temperature: f64,
507 pub d33: f64,
509 pub d31: f64,
511 pub permittivity_ferroelectric: f64,
513}
514
515impl DielectricCeramic {
516 pub fn new(
518 curie_constant: f64,
519 curie_temperature: f64,
520 d33: f64,
521 d31: f64,
522 permittivity_ferroelectric: f64,
523 ) -> Self {
524 Self {
525 curie_constant,
526 curie_temperature,
527 d33,
528 d31,
529 permittivity_ferroelectric,
530 }
531 }
532
533 pub fn barium_titanate() -> Self {
535 Self::new(1.7e5, 393.0, 190.0, -79.0, 4000.0)
536 }
537
538 pub fn permittivity_above_tc(&self, temp: f64) -> Option<f64> {
542 if temp <= self.curie_temperature {
543 None
544 } else {
545 Some(self.curie_constant / (temp - self.curie_temperature))
546 }
547 }
548
549 pub fn permittivity(&self, temp: f64) -> f64 {
552 self.permittivity_above_tc(temp)
553 .unwrap_or(self.permittivity_ferroelectric)
554 }
555
556 pub fn electric_displacement(&self, e_field: f64, temp: f64) -> f64 {
560 const EPS0: f64 = 8.854_187_817e-12;
561 EPS0 * self.permittivity(temp) * e_field
562 }
563
564 pub fn piezo_strain_33(&self, e3: f64) -> f64 {
568 self.d33 * 1e-12 * e3
569 }
570}
571
572#[derive(Debug, Clone)]
580pub struct ZrO2Transformation {
581 pub transformation_strain: f64,
583 pub volume_fraction: f64,
585 pub shear_modulus: f64,
587 pub base_toughness: f64,
589 pub zone_height: f64,
591}
592
593impl ZrO2Transformation {
594 pub fn new(
596 transformation_strain: f64,
597 volume_fraction: f64,
598 shear_modulus: f64,
599 base_toughness: f64,
600 zone_height: f64,
601 ) -> Self {
602 Self {
603 transformation_strain,
604 volume_fraction,
605 shear_modulus,
606 base_toughness,
607 zone_height,
608 }
609 }
610
611 pub fn toughening_increment(&self) -> f64 {
616 let e_eff = 2.0 * self.shear_modulus;
617 0.22 * e_eff * self.transformation_strain * self.volume_fraction * self.zone_height.sqrt()
618 }
619
620 pub fn effective_toughness(&self) -> f64 {
622 self.base_toughness + self.toughening_increment()
623 }
624
625 pub fn zone_size_estimate(&self) -> f64 {
630 let sigma_t =
631 self.shear_modulus * self.transformation_strain / (1.0 - self.volume_fraction);
632 let kic = self.effective_toughness();
633 (kic / sigma_t).powi(2) / (3.0 * PI)
634 }
635}
636
637#[cfg(test)]
642mod tests {
643 use super::*;
644
645 #[test]
648 fn test_alumina_young_modulus() {
649 let al2o3 = CeramicProperties::alumina();
650 assert!(
651 (al2o3.young_modulus - 380e9).abs() < 1e6,
652 "E(Al2O3) should be ~380 GPa"
653 );
654 }
655
656 #[test]
657 fn test_sic_fracture_toughness() {
658 let sic = CeramicProperties::silicon_carbide();
659 assert!((sic.fracture_toughness - 4.5).abs() < 0.01);
660 }
661
662 #[test]
663 fn test_zirconia_high_toughness() {
664 let zro2 = CeramicProperties::zirconia();
665 assert!(zro2.fracture_toughness > CeramicProperties::alumina().fracture_toughness);
666 }
667
668 #[test]
669 fn test_titania_properties_positive() {
670 let tio2 = CeramicProperties::titania();
671 assert!(tio2.young_modulus > 0.0);
672 assert!(tio2.thermal_conductivity > 0.0);
673 assert!(tio2.cte > 0.0);
674 }
675
676 #[test]
679 fn test_griffith_larger_crack_lower_strength() {
680 let props = CeramicProperties::alumina();
681 let frac = BrittleFractureCeramic::new(props, 1.12);
682 let sigma_small = frac.griffith_critical_stress(1e-6);
683 let sigma_large = frac.griffith_critical_stress(100e-6);
684 assert!(
685 sigma_small > sigma_large,
686 "Larger crack → lower Griffith strength"
687 );
688 }
689
690 #[test]
691 fn test_stress_intensity_proportional_to_sigma() {
692 let props = CeramicProperties::alumina();
693 let frac = BrittleFractureCeramic::new(props, 1.12);
694 let k1 = frac.stress_intensity(100e6, 10e-6);
695 let k2 = frac.stress_intensity(200e6, 10e-6);
696 assert!((k2 / k1 - 2.0).abs() < 1e-10);
697 }
698
699 #[test]
700 fn test_is_critical_small_crack_not_critical() {
701 let props = CeramicProperties::alumina();
702 let frac = BrittleFractureCeramic::new(props, 1.12);
703 assert!(!frac.is_critical(100e6, 1e-9));
705 }
706
707 #[test]
708 fn test_is_critical_large_crack_is_critical() {
709 let props = CeramicProperties::alumina();
710 let frac = BrittleFractureCeramic::new(props, 1.12);
711 assert!(frac.is_critical(400e6, 1e-3));
713 }
714
715 #[test]
716 fn test_critical_crack_length_decreases_with_stress() {
717 let props = CeramicProperties::alumina();
718 let frac = BrittleFractureCeramic::new(props, 1.12);
719 let a1 = frac.critical_crack_length(100e6);
720 let a2 = frac.critical_crack_length(400e6);
721 assert!(a1 > a2, "Higher stress → shorter critical crack");
722 }
723
724 #[test]
725 fn test_weibull_survival_at_zero_stress() {
726 let props = CeramicProperties::alumina();
727 let frac = BrittleFractureCeramic::new(props, 1.12);
728 let ps = frac.weibull_survival_probability(0.0);
729 assert!((ps - 1.0).abs() < 1e-12, "Survival at zero stress = 1");
730 }
731
732 #[test]
733 fn test_weibull_survival_at_characteristic_strength() {
734 let props = CeramicProperties::alumina();
735 let frac = BrittleFractureCeramic::new(props.clone(), 1.12);
736 let ps = frac.weibull_survival_probability(props.flexural_strength);
737 assert!((ps - (-1.0_f64).exp()).abs() < 1e-10);
739 }
740
741 #[test]
742 fn test_weibull_failure_probability_complement() {
743 let props = CeramicProperties::alumina();
744 let frac = BrittleFractureCeramic::new(props, 1.12);
745 let sigma = 300e6;
746 let ps = frac.weibull_survival_probability(sigma);
747 let pf = frac.weibull_failure_probability(sigma);
748 assert!((ps + pf - 1.0).abs() < 1e-12);
749 }
750
751 #[test]
752 fn test_weibull_high_modulus_steep_transition() {
753 let mut props = CeramicProperties::alumina();
755 props.weibull_modulus = 100.0;
756 let frac = BrittleFractureCeramic::new(props.clone(), 1.12);
757 let pf_below = frac.weibull_failure_probability(0.99 * props.flexural_strength);
758 let pf_above = frac.weibull_failure_probability(1.01 * props.flexural_strength);
759 assert!(pf_above - pf_below > 0.5, "High m → steep transition");
760 }
761
762 #[test]
763 fn test_weibull_median_less_than_characteristic() {
764 let props = CeramicProperties::alumina();
765 let frac = BrittleFractureCeramic::new(props.clone(), 1.12);
766 let median = frac.weibull_median_strength();
767 assert!(median < props.flexural_strength);
768 }
769
770 #[test]
771 fn test_weibull_mean_strength_positive() {
772 let props = CeramicProperties::silicon_nitride();
773 let frac = BrittleFractureCeramic::new(props, 1.12);
774 assert!(frac.weibull_mean_strength() > 0.0);
775 }
776
777 #[test]
780 fn test_r_parameter_increases_with_strength() {
781 let mut p1 = CeramicProperties::alumina();
782 let mut p2 = CeramicProperties::alumina();
783 p2.flexural_strength *= 2.0;
784 let r1 = ThermalShockResistance::new(p1.clone()).r_parameter();
785 let r2 = ThermalShockResistance::new(p2).r_parameter();
786 assert!(r2 > r1);
787 p1.name = p1.name.clone();
789 }
790
791 #[test]
792 fn test_r_parameter_decreases_with_youngs_modulus() {
793 let mut p1 = CeramicProperties::alumina();
794 let mut p2 = CeramicProperties::alumina();
795 p2.young_modulus *= 2.0;
796 let r1 = ThermalShockResistance::new(p1.clone()).r_parameter();
797 let r2 = ThermalShockResistance::new(p2).r_parameter();
798 assert!(r1 > r2, "Higher E → lower R");
799 p1.name = p1.name.clone();
800 }
801
802 #[test]
803 fn test_r_prime_greater_than_r_for_high_k() {
804 let props = CeramicProperties::silicon_carbide(); let tsr = ThermalShockResistance::new(props);
806 assert!(tsr.r_prime_parameter() > tsr.r_parameter());
807 }
808
809 #[test]
810 fn test_r_damage_parameter_positive() {
811 let props = CeramicProperties::zirconia();
812 let tsr = ThermalShockResistance::new(props);
813 assert!(tsr.r_damage_parameter() > 0.0);
814 }
815
816 #[test]
817 fn test_critical_temperature_difference_equals_r() {
818 let props = CeramicProperties::alumina();
819 let tsr = ThermalShockResistance::new(props);
820 assert!((tsr.critical_temperature_difference() - tsr.r_parameter()).abs() < 1e-12);
821 }
822
823 #[test]
826 fn test_density_increases_with_time() {
827 let model = SinteringModel::new(1e-5, 5.0, 300e3, 1e-6, 1e-8, 3.0, 450e3, 0.60);
828 let rho_early = model.density_at_time(100.0, 1e-6, 1800.0, 100);
829 let rho_late = model.density_at_time(10000.0, 1e-6, 1800.0, 100);
830 assert!(
831 rho_late > rho_early,
832 "Density should increase with sintering time"
833 );
834 }
835
836 #[test]
837 fn test_density_bounded_by_unity() {
838 let model = SinteringModel::new(1e-5, 5.0, 100e3, 1e-6, 1e-5, 3.0, 100e3, 0.60);
839 let rho = model.density_at_time(1e9, 1e-6, 2000.0, 1000);
840 assert!(rho <= 1.0, "Density cannot exceed 1");
841 }
842
843 #[test]
844 fn test_neck_ratio_increases_with_time() {
845 let model = SinteringModel::new(1e-10, 5.0, 300e3, 1e-6, 1e-8, 3.0, 450e3, 0.60);
846 let xr_early = model.neck_ratio(100.0, 1600.0);
847 let xr_late = model.neck_ratio(10000.0, 1600.0);
848 assert!(xr_late > xr_early);
849 }
850
851 #[test]
852 fn test_neck_ratio_increases_with_temperature() {
853 let model = SinteringModel::new(1e-10, 5.0, 300e3, 1e-6, 1e-8, 3.0, 450e3, 0.60);
854 let xr_low = model.neck_ratio(1000.0, 1400.0);
855 let xr_high = model.neck_ratio(1000.0, 1800.0);
856 assert!(xr_high > xr_low);
857 }
858
859 #[test]
860 fn test_densification_rate_positive() {
861 let model = SinteringModel::new(1e-10, 5.0, 300e3, 1e-6, 1e-8, 3.0, 450e3, 0.60);
862 let rate = model.densification_rate(1e-6, 1800.0);
863 assert!(rate > 0.0);
864 }
865
866 #[test]
869 fn test_grain_growth_increases_with_time() {
870 let gg = GrainGrowth::new(2.0, 1e-14, 300e3, 1e-6);
871 let d1 = gg.grain_size(100.0, 1500.0);
872 let d2 = gg.grain_size(10000.0, 1500.0);
873 assert!(d2 > d1, "Grain size should increase with annealing time");
874 }
875
876 #[test]
877 fn test_grain_size_at_zero_time_equals_initial() {
878 let d0 = 1.5e-6;
879 let gg = GrainGrowth::new(2.0, 1e-14, 300e3, d0);
880 let d = gg.grain_size(0.0, 1500.0);
881 assert!((d - d0).abs() < 1e-15, "d(t=0) should equal d0");
882 }
883
884 #[test]
885 fn test_grain_growth_faster_at_higher_temp() {
886 let gg = GrainGrowth::new(2.0, 1e-14, 300e3, 1e-6);
887 let d_low = gg.grain_size(3600.0, 1200.0);
888 let d_high = gg.grain_size(3600.0, 1600.0);
889 assert!(d_high > d_low);
890 }
891
892 #[test]
893 fn test_grain_growth_exponent_effect() {
894 let d0 = 1e-6_f64;
898 let gg_n2 = GrainGrowth::new(2.0, 1e-10, 0.0, d0); let gg_n3 = GrainGrowth::new(3.0, 1e-10, 0.0, d0);
900 let d_n2 = gg_n2.grain_size(1.0, 1000.0);
901 let d_n3 = gg_n3.grain_size(1.0, 1000.0);
902 assert!(d_n2 > d0, "n=2 grain size should grow beyond initial");
906 assert!(d_n3 > d0, "n=3 grain size should grow beyond initial");
907 assert!(d_n2 > d0 * 1.5 || d_n3 > d0 * 1.5);
909 }
910
911 #[test]
912 fn test_growth_rate_positive() {
913 let gg = GrainGrowth::new(2.0, 1e-14, 300e3, 1e-6);
914 assert!(gg.growth_rate(2e-6, 1500.0) > 0.0);
915 }
916
917 #[test]
920 fn test_creep_rate_exponential_temperature() {
921 let creep = CreepCeramic::new(1e-30, 3.0, 500e3);
922 let rate_low = creep.creep_rate(100e6, 1000.0);
923 let rate_high = creep.creep_rate(100e6, 1400.0);
924 assert!(
925 rate_high > rate_low * 100.0,
926 "Creep rate increases strongly with temperature"
927 );
928 }
929
930 #[test]
931 fn test_creep_rate_power_law_stress() {
932 let creep = CreepCeramic::new(1e-30, 3.0, 500e3);
933 let r1 = creep.creep_rate(100e6, 1200.0);
934 let r2 = creep.creep_rate(200e6, 1200.0);
935 assert!((r2 / r1 - 8.0).abs() < 1e-10);
937 }
938
939 #[test]
940 fn test_accumulated_strain_proportional_to_time() {
941 let creep = CreepCeramic::new(1e-30, 3.0, 500e3);
942 let eps1 = creep.accumulated_strain(100e6, 1200.0, 1000.0);
943 let eps2 = creep.accumulated_strain(100e6, 1200.0, 2000.0);
944 assert!((eps2 / eps1 - 2.0).abs() < 1e-12);
945 }
946
947 #[test]
948 fn test_apparent_activation_energy_estimate() {
949 let creep = CreepCeramic::new(1e-30, 3.0, 500e3);
950 let t1 = 1200.0_f64;
951 let t2 = 1400.0_f64;
952 let r1 = creep.creep_rate(100e6, t1);
953 let r2 = creep.creep_rate(100e6, t2);
954 let q_est = CreepCeramic::apparent_activation_energy(r1, t1, r2, t2);
955 assert!(
956 (q_est - 500e3).abs() / 500e3 < 0.01,
957 "Q estimate should be within 1% of true value"
958 );
959 }
960
961 #[test]
964 fn test_curie_weiss_diverges_near_tc() {
965 let bto = DielectricCeramic::barium_titanate();
966 let eps_near = bto
967 .permittivity_above_tc(bto.curie_temperature + 1.0)
968 .unwrap();
969 let eps_far = bto
970 .permittivity_above_tc(bto.curie_temperature + 100.0)
971 .unwrap();
972 assert!(eps_near > eps_far * 10.0, "ε_r diverges near T_C");
973 }
974
975 #[test]
976 fn test_permittivity_below_tc_returns_ferroelectric_value() {
977 let bto = DielectricCeramic::barium_titanate();
978 let eps = bto.permittivity(300.0); assert!((eps - bto.permittivity_ferroelectric).abs() < 1e-12);
980 }
981
982 #[test]
983 fn test_permittivity_above_tc_is_none_at_tc() {
984 let bto = DielectricCeramic::barium_titanate();
985 assert!(bto.permittivity_above_tc(bto.curie_temperature).is_none());
986 }
987
988 #[test]
989 fn test_electric_displacement_proportional_to_field() {
990 let bto = DielectricCeramic::barium_titanate();
991 let d1 = bto.electric_displacement(1e5, 500.0);
992 let d2 = bto.electric_displacement(2e5, 500.0);
993 assert!((d2 / d1 - 2.0).abs() < 1e-10);
994 }
995
996 #[test]
997 fn test_piezo_strain_33_sign() {
998 let bto = DielectricCeramic::barium_titanate();
999 assert!(bto.piezo_strain_33(1e6) > 0.0);
1001 }
1002
1003 #[test]
1006 fn test_toughening_increment_positive() {
1007 let zro2 = ZrO2Transformation::new(0.04, 0.3, 80e9, 5e6, 1e-4);
1008 assert!(zro2.toughening_increment() > 0.0);
1009 }
1010
1011 #[test]
1012 fn test_effective_toughness_greater_than_base() {
1013 let zro2 = ZrO2Transformation::new(0.04, 0.3, 80e9, 5e6, 1e-4);
1014 assert!(zro2.effective_toughness() > zro2.base_toughness);
1015 }
1016
1017 #[test]
1018 fn test_toughening_increases_with_volume_fraction() {
1019 let zro2_low = ZrO2Transformation::new(0.04, 0.1, 80e9, 5e6, 1e-4);
1020 let zro2_high = ZrO2Transformation::new(0.04, 0.4, 80e9, 5e6, 1e-4);
1021 assert!(zro2_high.toughening_increment() > zro2_low.toughening_increment());
1022 }
1023
1024 #[test]
1025 fn test_zone_size_estimate_positive() {
1026 let zro2 = ZrO2Transformation::new(0.04, 0.3, 80e9, 5e6, 1e-4);
1027 assert!(zro2.zone_size_estimate() > 0.0);
1028 }
1029
1030 #[test]
1031 fn test_toughening_increases_with_strain() {
1032 let zro2_low = ZrO2Transformation::new(0.02, 0.3, 80e9, 5e6, 1e-4);
1033 let zro2_high = ZrO2Transformation::new(0.06, 0.3, 80e9, 5e6, 1e-4);
1034 assert!(zro2_high.toughening_increment() > zro2_low.toughening_increment());
1035 }
1036}