1#![allow(missing_docs)]
11#![allow(dead_code)]
12
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct PyElasticMaterial {
22 pub youngs_modulus: f64,
24 pub poisson_ratio: f64,
26 pub density: f64,
28}
29
30impl PyElasticMaterial {
31 pub fn new(youngs_modulus: f64, poisson_ratio: f64, density: f64) -> Self {
33 Self {
34 youngs_modulus,
35 poisson_ratio,
36 density,
37 }
38 }
39
40 pub fn stress_from_strain(&self, strain: [f64; 3]) -> [f64; 3] {
44 let e = self.youngs_modulus;
45 let nu = self.poisson_ratio;
46 let c = e / (1.0 - nu * nu);
47 [
48 c * (strain[0] + nu * strain[1]),
49 c * (nu * strain[0] + strain[1]),
50 c * (1.0 - nu) * 0.5 * strain[2],
51 ]
52 }
53
54 pub fn strain_energy_density(&self, strain: [f64; 3]) -> f64 {
56 let stress = self.stress_from_strain(strain);
57 0.5 * (stress[0] * strain[0] + stress[1] * strain[1] + stress[2] * strain[2])
58 }
59
60 pub fn shear_modulus(&self) -> f64 {
62 self.youngs_modulus / (2.0 * (1.0 + self.poisson_ratio))
63 }
64
65 pub fn bulk_modulus(&self) -> f64 {
67 self.youngs_modulus / (3.0 * (1.0 - 2.0 * self.poisson_ratio))
68 }
69}
70
71impl Default for PyElasticMaterial {
72 fn default() -> Self {
73 Self::new(200e9, 0.3, 7850.0)
75 }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct PyHyperelasticMaterial {
85 pub c1: f64,
87 pub c2: f64,
89 pub bulk_modulus: f64,
91}
92
93impl PyHyperelasticMaterial {
94 pub fn new(c1: f64, c2: f64, bulk_modulus: f64) -> Self {
96 Self {
97 c1,
98 c2,
99 bulk_modulus,
100 }
101 }
102
103 pub fn strain_energy_neo_hookean(&self, i1_bar: f64, j: f64) -> f64 {
108 self.c1 * (i1_bar - 3.0) + 0.5 * self.bulk_modulus * (j - 1.0).powi(2)
109 }
110
111 pub fn strain_energy_mooney_rivlin(&self, i1_bar: f64, i2_bar: f64, j: f64) -> f64 {
116 self.c1 * (i1_bar - 3.0)
117 + self.c2 * (i2_bar - 3.0)
118 + 0.5 * self.bulk_modulus * (j - 1.0).powi(2)
119 }
120
121 pub fn initial_shear_modulus(&self) -> f64 {
123 2.0 * (self.c1 + self.c2)
124 }
125}
126
127impl Default for PyHyperelasticMaterial {
128 fn default() -> Self {
129 Self::new(0.4e6, 0.1e6, 2.0e9)
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct PyPlasticMaterial {
141 pub yield_stress: f64,
143 pub hardening_modulus: f64,
145 pub youngs_modulus: f64,
147}
148
149impl PyPlasticMaterial {
150 pub fn new(yield_stress: f64, hardening_modulus: f64, youngs_modulus: f64) -> Self {
152 Self {
153 yield_stress,
154 hardening_modulus,
155 youngs_modulus,
156 }
157 }
158
159 pub fn yield_function(&self, equivalent_stress: f64, accumulated_plastic_strain: f64) -> f64 {
163 let hardened_yield =
164 self.yield_stress + self.hardening_modulus * accumulated_plastic_strain;
165 equivalent_stress - hardened_yield
166 }
167
168 pub fn current_yield_stress(&self, accumulated_plastic_strain: f64) -> f64 {
170 self.yield_stress + self.hardening_modulus * accumulated_plastic_strain
171 }
172}
173
174impl Default for PyPlasticMaterial {
175 fn default() -> Self {
176 Self::new(250e6, 10e9, 200e9)
177 }
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct PyFatigueMaterial {
187 pub basquin_coefficient: f64,
189 pub basquin_exponent: f64,
191 pub endurance_limit: f64,
193}
194
195impl PyFatigueMaterial {
196 pub fn new(basquin_coefficient: f64, basquin_exponent: f64, endurance_limit: f64) -> Self {
198 Self {
199 basquin_coefficient,
200 basquin_exponent,
201 endurance_limit,
202 }
203 }
204
205 pub fn cycles_to_failure(&self, stress_amplitude: f64) -> f64 {
210 if stress_amplitude <= self.endurance_limit {
211 return f64::INFINITY;
212 }
213 (self.basquin_coefficient / stress_amplitude).powf(1.0 / self.basquin_exponent)
215 }
216
217 pub fn miner_damage(&self, n_cycles: f64, stress_amplitude: f64) -> f64 {
219 let nf = self.cycles_to_failure(stress_amplitude);
220 if nf.is_infinite() { 0.0 } else { n_cycles / nf }
221 }
222}
223
224impl Default for PyFatigueMaterial {
225 fn default() -> Self {
226 Self::new(900e6, 0.1, 200e6)
228 }
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct PyThermalMaterial {
238 pub conductivity: f64,
240 pub specific_heat: f64,
242 pub density: f64,
244}
245
246impl PyThermalMaterial {
247 pub fn new(conductivity: f64, specific_heat: f64, density: f64) -> Self {
249 Self {
250 conductivity,
251 specific_heat,
252 density,
253 }
254 }
255
256 pub fn thermal_diffusivity(&self) -> f64 {
258 self.conductivity / (self.density * self.specific_heat)
259 }
260
261 pub fn heat_flux(&self, grad_t_magnitude: f64) -> f64 {
263 self.conductivity * grad_t_magnitude
264 }
265}
266
267impl Default for PyThermalMaterial {
268 fn default() -> Self {
269 Self::new(50.0, 480.0, 7850.0)
271 }
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct PyViscoelasticMaterial {
281 pub equilibrium_modulus: f64,
283 pub prony_moduli: Vec<f64>,
285 pub prony_times: Vec<f64>,
287}
288
289impl PyViscoelasticMaterial {
290 pub fn new(equilibrium_modulus: f64, prony_moduli: Vec<f64>, prony_times: Vec<f64>) -> Self {
294 Self {
295 equilibrium_modulus,
296 prony_moduli,
297 prony_times,
298 }
299 }
300
301 pub fn relaxation_modulus(&self, t: f64) -> f64 {
303 let sum: f64 = self
304 .prony_moduli
305 .iter()
306 .zip(self.prony_times.iter())
307 .map(|(&ei, &ti)| ei * (-t / ti).exp())
308 .sum();
309 self.equilibrium_modulus + sum
310 }
311
312 pub fn glassy_modulus(&self) -> f64 {
314 self.equilibrium_modulus + self.prony_moduli.iter().sum::<f64>()
315 }
316}
317
318impl Default for PyViscoelasticMaterial {
319 fn default() -> Self {
320 Self::new(1e6, vec![2e6, 1e6], vec![0.1, 1.0])
321 }
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct PyDamageMaterial {
331 pub critical_energy_release: f64,
333 pub softening_slope: f64,
335 pub undamaged_modulus: f64,
337}
338
339impl PyDamageMaterial {
340 pub fn new(critical_energy_release: f64, softening_slope: f64, undamaged_modulus: f64) -> Self {
342 Self {
343 critical_energy_release,
344 softening_slope,
345 undamaged_modulus,
346 }
347 }
348
349 pub fn damage_variable(&self, strain_energy: f64) -> f64 {
353 if strain_energy <= 0.0 {
354 return 0.0;
355 }
356 let d = (strain_energy - self.critical_energy_release)
357 / (self.softening_slope.abs() + strain_energy);
358 d.clamp(0.0, 1.0)
359 }
360
361 pub fn effective_modulus(&self, strain_energy: f64) -> f64 {
363 let d = self.damage_variable(strain_energy);
364 (1.0 - d) * self.undamaged_modulus
365 }
366}
367
368impl Default for PyDamageMaterial {
369 fn default() -> Self {
370 Self::new(1000.0, 1e8, 200e9)
371 }
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
380pub struct PyCreepMaterial {
381 pub norton_coefficient: f64,
383 pub norton_exponent: f64,
385 pub activation_temperature: f64,
387}
388
389impl PyCreepMaterial {
390 pub fn new(norton_coefficient: f64, norton_exponent: f64, activation_temperature: f64) -> Self {
392 Self {
393 norton_coefficient,
394 norton_exponent,
395 activation_temperature,
396 }
397 }
398
399 pub fn creep_rate(&self, stress: f64, temp: f64) -> f64 {
403 let arrhenius = (-self.activation_temperature / temp).exp();
404 self.norton_coefficient * stress.powf(self.norton_exponent) * arrhenius
405 }
406
407 pub fn reference_creep_rate(&self, stress: f64) -> f64 {
409 self.creep_rate(stress, 293.0)
410 }
411}
412
413impl Default for PyCreepMaterial {
414 fn default() -> Self {
415 Self::new(1e-26, 5.0, 15_000.0)
417 }
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
426pub struct PyAcousticMaterial {
427 pub bulk_modulus: f64,
429 pub density: f64,
431}
432
433impl PyAcousticMaterial {
434 pub fn new(bulk_modulus: f64, density: f64) -> Self {
436 Self {
437 bulk_modulus,
438 density,
439 }
440 }
441
442 pub fn speed_of_sound(&self) -> f64 {
444 (self.bulk_modulus / self.density).sqrt()
445 }
446
447 pub fn impedance(&self) -> f64 {
449 self.density * self.speed_of_sound()
450 }
451
452 pub fn reflection_coefficient(&self, other: &PyAcousticMaterial) -> f64 {
454 let z1 = self.impedance();
455 let z2 = other.impedance();
456 (z2 - z1) / (z2 + z1)
457 }
458}
459
460impl Default for PyAcousticMaterial {
461 fn default() -> Self {
462 Self::new(2.2e9, 998.0)
464 }
465}
466
467#[derive(Debug, Clone, Serialize, Deserialize)]
473pub struct PyCompositeMaterial {
474 pub modulus1: f64,
476 pub modulus2: f64,
478 pub volume_fraction1: f64,
480}
481
482impl PyCompositeMaterial {
483 pub fn new(modulus1: f64, modulus2: f64, volume_fraction1: f64) -> Self {
485 Self {
486 modulus1,
487 modulus2,
488 volume_fraction1,
489 }
490 }
491
492 pub fn voigt_modulus(&self, vf1: f64, e1: f64, e2: f64) -> f64 {
494 vf1 * e1 + (1.0 - vf1) * e2
495 }
496
497 pub fn reuss_modulus(&self, vf1: f64, e1: f64, e2: f64) -> f64 {
499 let vf2 = 1.0 - vf1;
500 if vf1 / e1 + vf2 / e2 == 0.0 {
501 0.0
502 } else {
503 1.0 / (vf1 / e1 + vf2 / e2)
504 }
505 }
506
507 pub fn hill_modulus(&self) -> f64 {
509 let v = self.voigt_modulus(self.volume_fraction1, self.modulus1, self.modulus2);
510 let r = self.reuss_modulus(self.volume_fraction1, self.modulus1, self.modulus2);
511 0.5 * (v + r)
512 }
513
514 pub fn hashin_shtrikman_lower(&self) -> f64 {
516 let vf2 = 1.0 - self.volume_fraction1;
517 let e1 = self.modulus1.min(self.modulus2);
518 let e2 = self.modulus1.max(self.modulus2);
519 e1 + vf2 / (1.0 / (e2 - e1) + self.volume_fraction1 / (3.0 * e1))
520 }
521}
522
523impl Default for PyCompositeMaterial {
524 fn default() -> Self {
525 Self::new(70e9, 200e9, 0.6)
526 }
527}
528
529pub fn register_materials_module(_m: &str) {
539 }
541
542#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
553 fn test_elastic_new() {
554 let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
555 assert_eq!(mat.youngs_modulus, 200e9);
556 assert_eq!(mat.poisson_ratio, 0.3);
557 assert_eq!(mat.density, 7850.0);
558 }
559
560 #[test]
561 fn test_elastic_default() {
562 let mat = PyElasticMaterial::default();
563 assert!(mat.youngs_modulus > 0.0);
564 }
565
566 #[test]
567 fn test_elastic_shear_modulus() {
568 let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
569 let g = mat.shear_modulus();
570 assert!((g - 76.923e9).abs() < 1e8);
572 }
573
574 #[test]
575 fn test_elastic_bulk_modulus() {
576 let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
577 let k = mat.bulk_modulus();
578 assert!((k - 166.667e9).abs() < 1e9);
580 }
581
582 #[test]
583 fn test_elastic_stress_from_strain_uniaxial() {
584 let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
585 let strain = [1e-3, 0.0, 0.0];
586 let stress = mat.stress_from_strain(strain);
587 assert!(stress[0] > 0.0);
589 assert!(stress[2].abs() < 1.0);
590 }
591
592 #[test]
593 fn test_elastic_strain_energy_positive() {
594 let mat = PyElasticMaterial::new(200e9, 0.3, 7850.0);
595 let e = mat.strain_energy_density([1e-3, 0.0, 0.0]);
596 assert!(e > 0.0);
597 }
598
599 #[test]
600 fn test_elastic_zero_strain_zero_stress() {
601 let mat = PyElasticMaterial::default();
602 let s = mat.stress_from_strain([0.0, 0.0, 0.0]);
603 assert_eq!(s, [0.0, 0.0, 0.0]);
604 }
605
606 #[test]
609 fn test_hyperelastic_new() {
610 let mat = PyHyperelasticMaterial::new(0.4e6, 0.1e6, 2.0e9);
611 assert_eq!(mat.c1, 0.4e6);
612 assert_eq!(mat.c2, 0.1e6);
613 assert_eq!(mat.bulk_modulus, 2.0e9);
614 }
615
616 #[test]
617 fn test_hyperelastic_neo_hookean_zero() {
618 let mat = PyHyperelasticMaterial::default();
619 let w = mat.strain_energy_neo_hookean(3.0, 1.0);
621 assert!(w.abs() < 1e-10);
622 }
623
624 #[test]
625 fn test_hyperelastic_mooney_rivlin_zero() {
626 let mat = PyHyperelasticMaterial::default();
627 let w = mat.strain_energy_mooney_rivlin(3.0, 3.0, 1.0);
628 assert!(w.abs() < 1e-10);
629 }
630
631 #[test]
632 fn test_hyperelastic_neo_hookean_positive_stretch() {
633 let mat = PyHyperelasticMaterial::new(0.4e6, 0.0, 2.0e9);
634 let w = mat.strain_energy_neo_hookean(4.0, 1.0);
635 assert!(w > 0.0);
636 }
637
638 #[test]
639 fn test_hyperelastic_initial_shear_modulus() {
640 let mat = PyHyperelasticMaterial::new(0.4e6, 0.1e6, 2.0e9);
641 let mu = mat.initial_shear_modulus();
642 assert!((mu - 1.0e6).abs() < 1.0);
643 }
644
645 #[test]
646 fn test_hyperelastic_default() {
647 let mat = PyHyperelasticMaterial::default();
648 assert!(mat.c1 > 0.0);
649 assert!(mat.bulk_modulus > 0.0);
650 }
651
652 #[test]
655 fn test_plastic_new() {
656 let mat = PyPlasticMaterial::new(250e6, 10e9, 200e9);
657 assert_eq!(mat.yield_stress, 250e6);
658 }
659
660 #[test]
661 fn test_plastic_yield_function_elastic() {
662 let mat = PyPlasticMaterial::new(250e6, 10e9, 200e9);
663 let f = mat.yield_function(200e6, 0.0);
664 assert!(f < 0.0);
665 }
666
667 #[test]
668 fn test_plastic_yield_function_yielding() {
669 let mat = PyPlasticMaterial::new(250e6, 10e9, 200e9);
670 let f = mat.yield_function(300e6, 0.0);
671 assert!(f > 0.0);
672 }
673
674 #[test]
675 fn test_plastic_hardening_increases_yield() {
676 let mat = PyPlasticMaterial::new(250e6, 10e9, 200e9);
677 let y0 = mat.current_yield_stress(0.0);
678 let y1 = mat.current_yield_stress(0.01);
679 assert!(y1 > y0);
680 }
681
682 #[test]
683 fn test_plastic_default() {
684 let mat = PyPlasticMaterial::default();
685 assert!(mat.yield_stress > 0.0);
686 }
687
688 #[test]
691 fn test_fatigue_new() {
692 let mat = PyFatigueMaterial::new(900e6, 0.1, 200e6);
693 assert_eq!(mat.basquin_coefficient, 900e6);
694 }
695
696 #[test]
697 fn test_fatigue_below_endurance_infinite_life() {
698 let mat = PyFatigueMaterial::default();
699 let n = mat.cycles_to_failure(100e6); assert!(n.is_infinite());
701 }
702
703 #[test]
704 fn test_fatigue_above_endurance_finite_life() {
705 let mat = PyFatigueMaterial::default();
706 let n = mat.cycles_to_failure(500e6);
707 assert!(n.is_finite() && n > 0.0);
708 }
709
710 #[test]
711 fn test_fatigue_miner_damage_zero_below_endurance() {
712 let mat = PyFatigueMaterial::default();
713 let d = mat.miner_damage(1000.0, 100e6);
714 assert_eq!(d, 0.0);
715 }
716
717 #[test]
718 fn test_fatigue_miner_damage_positive_above_endurance() {
719 let mat = PyFatigueMaterial::default();
720 let d = mat.miner_damage(1000.0, 500e6);
721 assert!(d > 0.0);
722 }
723
724 #[test]
725 fn test_fatigue_default() {
726 let mat = PyFatigueMaterial::default();
727 assert!(mat.endurance_limit > 0.0);
728 }
729
730 #[test]
733 fn test_thermal_new() {
734 let mat = PyThermalMaterial::new(50.0, 480.0, 7850.0);
735 assert_eq!(mat.conductivity, 50.0);
736 }
737
738 #[test]
739 fn test_thermal_diffusivity() {
740 let mat = PyThermalMaterial::new(50.0, 480.0, 7850.0);
741 let alpha = mat.thermal_diffusivity();
742 assert!(alpha > 0.0);
743 assert!((alpha - 1.328e-5).abs() < 1e-7);
745 }
746
747 #[test]
748 fn test_thermal_heat_flux() {
749 let mat = PyThermalMaterial::default();
750 let q = mat.heat_flux(10.0); assert!((q - 500.0).abs() < 1e-6);
752 }
753
754 #[test]
755 fn test_thermal_default() {
756 let mat = PyThermalMaterial::default();
757 assert!(mat.specific_heat > 0.0);
758 }
759
760 #[test]
763 fn test_viscoelastic_new() {
764 let mat = PyViscoelasticMaterial::new(1e6, vec![2e6], vec![0.1]);
765 assert_eq!(mat.equilibrium_modulus, 1e6);
766 }
767
768 #[test]
769 fn test_viscoelastic_relaxation_t0() {
770 let mat = PyViscoelasticMaterial::default();
771 let e0 = mat.relaxation_modulus(0.0);
772 let eg = mat.glassy_modulus();
773 assert!((e0 - eg).abs() < 1.0);
774 }
775
776 #[test]
777 fn test_viscoelastic_relaxation_large_t() {
778 let mat = PyViscoelasticMaterial::default();
779 let einf = mat.relaxation_modulus(1e12);
780 assert!((einf - mat.equilibrium_modulus).abs() < 1.0);
781 }
782
783 #[test]
784 fn test_viscoelastic_glassy_modulus() {
785 let mat = PyViscoelasticMaterial::new(1e6, vec![2e6, 1e6], vec![0.1, 1.0]);
786 assert!((mat.glassy_modulus() - 4e6).abs() < 1.0);
787 }
788
789 #[test]
790 fn test_viscoelastic_default() {
791 let mat = PyViscoelasticMaterial::default();
792 assert!(mat.prony_moduli.len() == mat.prony_times.len());
793 }
794
795 #[test]
798 fn test_damage_new() {
799 let mat = PyDamageMaterial::new(1000.0, 1e8, 200e9);
800 assert_eq!(mat.critical_energy_release, 1000.0);
801 }
802
803 #[test]
804 fn test_damage_zero_strain_energy() {
805 let mat = PyDamageMaterial::default();
806 assert_eq!(mat.damage_variable(0.0), 0.0);
807 }
808
809 #[test]
810 fn test_damage_below_critical() {
811 let mat = PyDamageMaterial::new(1000.0, 1e8, 200e9);
812 assert_eq!(mat.damage_variable(500.0), 0.0);
813 }
814
815 #[test]
816 fn test_damage_above_critical() {
817 let mat = PyDamageMaterial::new(100.0, 1e8, 200e9);
818 let d = mat.damage_variable(1000.0);
819 assert!(d > 0.0 && d <= 1.0);
820 }
821
822 #[test]
823 fn test_damage_effective_modulus_undamaged() {
824 let mat = PyDamageMaterial::new(1000.0, 1e8, 200e9);
825 let e = mat.effective_modulus(0.0);
826 assert!((e - 200e9).abs() < 1.0);
827 }
828
829 #[test]
830 fn test_damage_default() {
831 let mat = PyDamageMaterial::default();
832 assert!(mat.undamaged_modulus > 0.0);
833 }
834
835 #[test]
838 fn test_creep_new() {
839 let mat = PyCreepMaterial::new(1e-26, 5.0, 15_000.0);
840 assert_eq!(mat.norton_exponent, 5.0);
841 }
842
843 #[test]
844 fn test_creep_rate_positive() {
845 let mat = PyCreepMaterial::default();
846 let rate = mat.creep_rate(100e6, 800.0);
847 assert!(rate >= 0.0);
848 }
849
850 #[test]
851 fn test_creep_rate_increases_with_stress() {
852 let mat = PyCreepMaterial::default();
853 let r1 = mat.creep_rate(100e6, 800.0);
854 let r2 = mat.creep_rate(200e6, 800.0);
855 assert!(r2 > r1);
856 }
857
858 #[test]
859 fn test_creep_rate_decreases_with_temperature_drop() {
860 let mat = PyCreepMaterial::default();
861 let r_hot = mat.creep_rate(100e6, 1000.0);
862 let r_cold = mat.creep_rate(100e6, 500.0);
863 assert!(r_hot > r_cold);
864 }
865
866 #[test]
867 fn test_creep_reference_rate() {
868 let mat = PyCreepMaterial::default();
869 let r = mat.reference_creep_rate(100e6);
870 assert!(r >= 0.0);
871 }
872
873 #[test]
876 fn test_acoustic_new() {
877 let mat = PyAcousticMaterial::new(2.2e9, 998.0);
878 assert_eq!(mat.density, 998.0);
879 }
880
881 #[test]
882 fn test_acoustic_speed_of_sound_water() {
883 let mat = PyAcousticMaterial::default();
884 let c = mat.speed_of_sound();
885 assert!((c - 1484.0).abs() < 10.0);
887 }
888
889 #[test]
890 fn test_acoustic_impedance_positive() {
891 let mat = PyAcousticMaterial::default();
892 assert!(mat.impedance() > 0.0);
893 }
894
895 #[test]
896 fn test_acoustic_reflection_same_material() {
897 let mat = PyAcousticMaterial::default();
898 let r = mat.reflection_coefficient(&mat.clone());
899 assert!(r.abs() < 1e-10);
900 }
901
902 #[test]
903 fn test_acoustic_default() {
904 let mat = PyAcousticMaterial::default();
905 assert!(mat.bulk_modulus > 0.0);
906 }
907
908 #[test]
911 fn test_composite_new() {
912 let mat = PyCompositeMaterial::new(70e9, 200e9, 0.6);
913 assert_eq!(mat.volume_fraction1, 0.6);
914 }
915
916 #[test]
917 fn test_composite_voigt_bounds() {
918 let mat = PyCompositeMaterial::default();
919 let v = mat.voigt_modulus(0.6, 70e9, 200e9);
920 assert!((70e9..=200e9).contains(&v));
921 }
922
923 #[test]
924 fn test_composite_reuss_bounds() {
925 let mat = PyCompositeMaterial::default();
926 let r = mat.reuss_modulus(0.6, 70e9, 200e9);
927 assert!((70e9..=200e9).contains(&r));
928 }
929
930 #[test]
931 fn test_composite_voigt_ge_reuss() {
932 let mat = PyCompositeMaterial::default();
933 let v = mat.voigt_modulus(0.6, 70e9, 200e9);
934 let r = mat.reuss_modulus(0.6, 70e9, 200e9);
935 assert!(v >= r);
936 }
937
938 #[test]
939 fn test_composite_hill_between_voigt_reuss() {
940 let mat = PyCompositeMaterial::default();
941 let h = mat.hill_modulus();
942 let v = mat.voigt_modulus(mat.volume_fraction1, mat.modulus1, mat.modulus2);
943 let r = mat.reuss_modulus(mat.volume_fraction1, mat.modulus1, mat.modulus2);
944 assert!(h >= r && h <= v);
945 }
946
947 #[test]
948 fn test_composite_hashin_shtrikman_lower_positive() {
949 let mat = PyCompositeMaterial::default();
950 let hs = mat.hashin_shtrikman_lower();
951 assert!(hs > 0.0);
952 }
953
954 #[test]
955 fn test_composite_pure_phase1() {
956 let mat = PyCompositeMaterial::new(70e9, 200e9, 1.0);
957 let v = mat.voigt_modulus(1.0, 70e9, 200e9);
958 assert!((v - 70e9).abs() < 1.0);
959 }
960
961 #[test]
962 fn test_composite_default() {
963 let mat = PyCompositeMaterial::default();
964 assert!(mat.modulus1 > 0.0 && mat.modulus2 > 0.0);
965 }
966
967 #[test]
970 fn test_register_materials_module_no_panic() {
971 register_materials_module("materials");
972 }
973}