1#[allow(unused_imports)]
6use super::functions::*;
7#[derive(Debug, Clone, Copy)]
13pub struct Ply {
14 pub e1: f64,
16 pub e2: f64,
18 pub nu12: f64,
20 pub g12: f64,
22 pub thickness: f64,
24 pub angle_deg: f64,
26}
27impl Ply {
28 #[allow(clippy::too_many_arguments)]
30 pub fn new(e1: f64, e2: f64, nu12: f64, g12: f64, thickness: f64, angle_deg: f64) -> Self {
31 Self {
32 e1,
33 e2,
34 nu12,
35 g12,
36 thickness,
37 angle_deg,
38 }
39 }
40 pub fn nu21(&self) -> f64 {
42 self.nu12 * self.e2 / self.e1
43 }
44 pub fn reduced_stiffness(&self) -> [f64; 4] {
48 let nu21 = self.nu12 * self.e2 / self.e1;
49 let denom = 1.0 - self.nu12 * nu21;
50 let q11 = self.e1 / denom;
51 let q22 = self.e2 / denom;
52 let q12 = self.nu12 * self.e2 / denom;
53 let q66 = self.g12;
54 [q11, q22, q12, q66]
55 }
56 pub fn transformed_stiffness(&self) -> [[f64; 3]; 3] {
61 let [q11, q22, q12, q66] = self.reduced_stiffness();
62 let theta = self.angle_deg.to_radians();
63 let m = theta.cos();
64 let n = theta.sin();
65 let m2 = m * m;
66 let n2 = n * n;
67 let m4 = m2 * m2;
68 let n4 = n2 * n2;
69 let m2n2 = m2 * n2;
70 let m3n = m2 * m * n;
71 let mn3 = m * n2 * n;
72 let qb11 = q11 * m4 + 2.0 * (q12 + 2.0 * q66) * m2n2 + q22 * n4;
73 let qb22 = q11 * n4 + 2.0 * (q12 + 2.0 * q66) * m2n2 + q22 * m4;
74 let qb12 = (q11 + q22 - 4.0 * q66) * m2n2 + q12 * (m4 + n4);
75 let qb66 = (q11 + q22 - 2.0 * q12 - 2.0 * q66) * m2n2 + q66 * (m4 + n4);
76 let qb16 = (q11 - q12 - 2.0 * q66) * m3n - (q22 - q12 - 2.0 * q66) * mn3;
77 let qb26 = (q11 - q12 - 2.0 * q66) * mn3 - (q22 - q12 - 2.0 * q66) * m3n;
78 [[qb11, qb12, qb16], [qb12, qb22, qb26], [qb16, qb26, qb66]]
79 }
80}
81#[derive(Debug, Clone, Copy)]
86pub struct TransverselyIsotropic {
87 pub ea: f64,
89 pub et: f64,
91 pub nua: f64,
93 pub nut: f64,
95 pub ga: f64,
97}
98impl TransverselyIsotropic {
99 pub fn new(ea: f64, et: f64, nua: f64, nut: f64, ga: f64) -> Self {
101 Self {
102 ea,
103 et,
104 nua,
105 nut,
106 ga,
107 }
108 }
109 pub fn gt(&self) -> f64 {
111 self.et / (2.0 * (1.0 + self.nut))
112 }
113 pub fn to_orthotropic(&self) -> OrthotropicMaterial {
117 OrthotropicMaterial::new(
118 self.ea,
119 self.et,
120 self.et,
121 self.nua,
122 self.nut,
123 self.nua,
124 self.ga,
125 self.gt(),
126 self.ga,
127 )
128 }
129 pub fn constitutive_matrix(&self) -> [[f64; 6]; 6] {
131 self.to_orthotropic().constitutive_matrix()
132 }
133}
134#[derive(Debug, Clone, Copy)]
139pub struct ThermalConductivityTensor {
140 pub kappa: [[f64; 3]; 3],
142}
143impl ThermalConductivityTensor {
144 pub fn new(kappa: [[f64; 3]; 3]) -> Self {
146 Self { kappa }
147 }
148 pub fn isotropic(k: f64) -> Self {
150 Self::orthotropic(k, k, k)
151 }
152 pub fn orthotropic(kx: f64, ky: f64, kz: f64) -> Self {
156 let mut k = [[0.0_f64; 3]; 3];
157 k[0][0] = kx;
158 k[1][1] = ky;
159 k[2][2] = kz;
160 Self::new(k)
161 }
162 #[allow(clippy::needless_range_loop)]
166 pub fn heat_flux(&self, grad_t: [f64; 3]) -> [f64; 3] {
167 let mut q = [0.0f64; 3];
168 for i in 0..3 {
169 for j in 0..3 {
170 q[i] -= self.kappa[i][j] * grad_t[j];
171 }
172 }
173 q
174 }
175 #[allow(clippy::needless_range_loop)]
179 pub fn effective_conductivity(&self, n: [f64; 3]) -> f64 {
180 let mut kn = [0.0f64; 3];
181 for i in 0..3 {
182 for j in 0..3 {
183 kn[i] += self.kappa[i][j] * n[j];
184 }
185 }
186 n[0] * kn[0] + n[1] * kn[1] + n[2] * kn[2]
187 }
188 pub fn mean_conductivity(&self) -> f64 {
190 (self.kappa[0][0] + self.kappa[1][1] + self.kappa[2][2]) / 3.0
191 }
192}
193#[derive(Debug, Clone, Copy)]
201pub struct LaRCFailureCriteria {
202 pub xt: f64,
204 pub xc: f64,
206 pub yis: f64,
208 pub sis: f64,
210 pub phi0: f64,
212 pub eta: f64,
214}
215impl LaRCFailureCriteria {
216 #[allow(clippy::too_many_arguments)]
218 pub fn new(xt: f64, xc: f64, yis: f64, sis: f64, phi0: f64, eta: f64) -> Self {
219 Self {
220 xt,
221 xc,
222 yis,
223 sis,
224 phi0,
225 eta,
226 }
227 }
228 pub fn default_carbon_epoxy() -> Self {
230 Self::new(2560.0e6, 1590.0e6, 160.0e6, 130.0e6, 0.015, 0.43)
231 }
232 pub fn fibre_kinking_index(&self, sigma11: f64, sigma22: f64, tau12: f64) -> f64 {
236 let _ = sigma22;
237 let phi = self.phi0;
238 let sigma_m = sigma11 * phi.sin().powi(2) + tau12 * 2.0 * phi.sin() * phi.cos();
239 let tau_m = (sigma11 - 0.0) * phi.sin() * phi.cos()
240 + tau12 * (phi.cos().powi(2) - phi.sin().powi(2));
241 let denominator = (self.sis - self.eta * sigma_m).max(1.0);
242 tau_m.abs() / denominator
243 }
244 pub fn matrix_cracking_index(&self, sigma22: f64, tau12: f64, tau23: f64) -> f64 {
248 let _ = tau23;
249 let a = (tau12 / self.sis).powi(2);
250 let b = if sigma22 > 0.0 {
251 (sigma22 / self.yis).powi(2)
252 } else {
253 0.0
254 };
255 (a + b).sqrt()
256 }
257}
258#[derive(Debug, Clone, Copy)]
263pub struct BraidedComposite {
264 pub vf: f64,
266 pub e_fibre: f64,
268 pub e_matrix: f64,
270 pub nu_fibre: f64,
272 pub nu_matrix: f64,
274 pub braid_angle_deg: f64,
276}
277impl BraidedComposite {
278 pub fn triaxial_braid(
282 vf: f64,
283 e_fibre: f64,
284 e_matrix: f64,
285 nu_fibre: f64,
286 nu_matrix: f64,
287 braid_angle_deg: f64,
288 ) -> Self {
289 Self {
290 vf,
291 e_fibre,
292 e_matrix,
293 nu_fibre,
294 nu_matrix,
295 braid_angle_deg,
296 }
297 }
298 fn vm(&self) -> f64 {
300 1.0 - self.vf
301 }
302 fn e_axial(&self) -> f64 {
304 self.vf * self.e_fibre + self.vm() * self.e_matrix
305 }
306 fn e_transverse(&self) -> f64 {
308 1.0 / (self.vf / self.e_fibre + self.vm() / self.e_matrix)
309 }
310 fn nu12(&self) -> f64 {
312 self.vf * self.nu_fibre + self.vm() * self.nu_matrix
313 }
314 fn g12(&self) -> f64 {
316 let gf = self.e_fibre / (2.0 * (1.0 + self.nu_fibre));
317 let gm = self.e_matrix / (2.0 * (1.0 + self.nu_matrix));
318 1.0 / (self.vf / gf + self.vm() / gm)
319 }
320 pub fn in_plane_modulus(&self) -> f64 {
325 let theta = self.braid_angle_deg.to_radians();
326 let m = theta.cos();
327 let n = theta.sin();
328 let m2 = m * m;
329 let n2 = n * n;
330 let m4 = m2 * m2;
331 let n4 = n2 * n2;
332 let m2n2 = m2 * n2;
333 let e1 = self.e_axial();
334 let e2 = self.e_transverse();
335 let nu12 = self.nu12();
336 let nu21 = nu12 * e2 / e1;
337 let g12 = self.g12();
338 let denom = 1.0 - nu12 * nu21;
339 let q11 = e1 / denom;
340 let q22 = e2 / denom;
341 let q12 = nu12 * e2 / denom;
342 let q66 = g12;
343 let qb11_bias = q11 * m4 + 2.0 * (q12 + 2.0 * q66) * m2n2 + q22 * n4;
344 let weight_axial = 1.0 / 3.0;
345 let weight_bias = 2.0 / 3.0;
346 weight_axial * q11 + weight_bias * qb11_bias
347 }
348 pub fn effective_shear_modulus(&self) -> f64 {
350 let theta = self.braid_angle_deg.to_radians();
351 let m = theta.cos();
352 let n = theta.sin();
353 let m2 = m * m;
354 let n2 = n * n;
355 let m4 = m2 * m2;
356 let n4 = n2 * n2;
357 let m2n2 = m2 * n2;
358 let e1 = self.e_axial();
359 let e2 = self.e_transverse();
360 let nu12 = self.nu12();
361 let nu21 = nu12 * e2 / e1;
362 let g12 = self.g12();
363 let denom = 1.0 - nu12 * nu21;
364 let q11 = e1 / denom;
365 let q22 = e2 / denom;
366 let q12 = nu12 * e2 / denom;
367 let q66 = g12;
368 let qb66_bias = (q11 + q22 - 2.0 * q12 - 2.0 * q66) * m2n2 + q66 * (m4 + n4);
369 (1.0 / 3.0) * q66 + (2.0 / 3.0) * qb66_bias
370 }
371}
372#[derive(Debug, Clone, Copy)]
386pub struct MonoclinicMaterial {
387 pub s11: f64,
389 pub s12: f64,
391 pub s13: f64,
393 pub s16: f64,
395 pub s22: f64,
397 pub s23: f64,
399 pub s26: f64,
401 pub s33: f64,
403 pub s36: f64,
405 pub s44: f64,
407 pub s45: f64,
409 pub s55: f64,
411 pub s66: f64,
413}
414impl MonoclinicMaterial {
415 #[allow(clippy::too_many_arguments)]
417 pub fn new(
418 s11: f64,
419 s12: f64,
420 s13: f64,
421 s16: f64,
422 s22: f64,
423 s23: f64,
424 s26: f64,
425 s33: f64,
426 s36: f64,
427 s44: f64,
428 s45: f64,
429 s55: f64,
430 s66: f64,
431 ) -> Self {
432 Self {
433 s11,
434 s12,
435 s13,
436 s16,
437 s22,
438 s23,
439 s26,
440 s33,
441 s36,
442 s44,
443 s45,
444 s55,
445 s66,
446 }
447 }
448 pub fn e1(&self) -> f64 {
450 1.0 / self.s11
451 }
452 pub fn e2(&self) -> f64 {
454 1.0 / self.s22
455 }
456 pub fn is_physically_valid(&self) -> bool {
460 self.s11 > 0.0 && self.s22 > 0.0 && self.s33 > 0.0
461 }
462}
463#[derive(Debug, Clone, Copy)]
465pub struct WovenLamina {
466 pub e11: f64,
468 pub e22: f64,
470 pub nu12: f64,
472 pub g12: f64,
474 pub e33: f64,
476 pub g13: f64,
478 pub g23: f64,
480}
481impl WovenLamina {
482 pub fn balanced_plain_weave(
489 fiber_vol: f64,
490 e_fiber: f64,
491 e_matrix: f64,
492 nu_fiber: f64,
493 nu_matrix: f64,
494 ) -> Self {
495 let vm = 1.0 - fiber_vol;
496 let vf = fiber_vol;
497 let e11 = vf * e_fiber + vm * e_matrix;
498 let e22 = e11;
499 let nu12 = vf * nu_fiber + vm * nu_matrix;
500 let g_fiber = e_fiber / (2.0 * (1.0 + nu_fiber));
501 let g_matrix = e_matrix / (2.0 * (1.0 + nu_matrix));
502 let g12 = 1.0 / (vf / g_fiber + vm / g_matrix);
503 let e33 = 1.0 / (vf / e_fiber + vm / e_matrix);
504 let g13 = 1.0 / (vf / g_fiber + vm / g_matrix);
505 let g23 = g13;
506 Self {
507 e11,
508 e22,
509 nu12,
510 g12,
511 e33,
512 g13,
513 g23,
514 }
515 }
516 pub fn constitutive_matrix_2d(&self) -> [[f64; 3]; 3] {
518 let nu21 = self.nu12 * self.e22 / self.e11;
519 let denom = 1.0 - self.nu12 * nu21;
520 let mut q = [[0.0_f64; 3]; 3];
521 q[0][0] = self.e11 / denom;
522 q[1][1] = self.e22 / denom;
523 q[0][1] = self.nu12 * self.e22 / denom;
524 q[1][0] = q[0][1];
525 q[2][2] = self.g12;
526 q
527 }
528}
529#[derive(Debug, Clone, Copy)]
544pub struct OrthotropicMaterial {
545 pub e1: f64,
547 pub e2: f64,
549 pub e3: f64,
551 pub nu12: f64,
553 pub nu23: f64,
555 pub nu13: f64,
557 pub g12: f64,
559 pub g23: f64,
561 pub g13: f64,
563}
564impl OrthotropicMaterial {
565 #[allow(clippy::too_many_arguments)]
567 pub fn new(
568 e1: f64,
569 e2: f64,
570 e3: f64,
571 nu12: f64,
572 nu23: f64,
573 nu13: f64,
574 g12: f64,
575 g23: f64,
576 g13: f64,
577 ) -> Self {
578 Self {
579 e1,
580 e2,
581 e3,
582 nu12,
583 nu23,
584 nu13,
585 g12,
586 g23,
587 g13,
588 }
589 }
590 pub fn carbon_fiber_epoxy() -> Self {
596 Self::new(140.0e9, 10.0e9, 10.0e9, 0.3, 0.4, 0.3, 5.0e9, 3.5e9, 5.0e9)
597 }
598 pub fn douglas_fir() -> Self {
604 Self::new(
605 13.5e9, 0.9e9, 0.75e9, 0.292, 0.37, 0.374, 0.88e9, 0.06e9, 1.07e9,
606 )
607 }
608 pub fn compliance_matrix(&self) -> [[f64; 6]; 6] {
612 let nu21 = self.nu12 * self.e2 / self.e1;
613 let nu31 = self.nu13 * self.e3 / self.e1;
614 let nu32 = self.nu23 * self.e3 / self.e2;
615 let mut s = [[0.0_f64; 6]; 6];
616 s[0][0] = 1.0 / self.e1;
617 s[1][1] = 1.0 / self.e2;
618 s[2][2] = 1.0 / self.e3;
619 s[0][1] = -nu21 / self.e2;
620 s[1][0] = -nu21 / self.e2;
621 s[0][2] = -nu31 / self.e3;
622 s[2][0] = -nu31 / self.e3;
623 s[1][2] = -nu32 / self.e3;
624 s[2][1] = -nu32 / self.e3;
625 s[3][3] = 1.0 / self.g12;
626 s[4][4] = 1.0 / self.g23;
627 s[5][5] = 1.0 / self.g13;
628 s
629 }
630 pub fn constitutive_matrix(&self) -> [[f64; 6]; 6] {
634 let nu21 = self.nu12 * self.e2 / self.e1;
635 let nu31 = self.nu13 * self.e3 / self.e1;
636 let nu32 = self.nu23 * self.e3 / self.e2;
637 let nu12 = self.nu12;
638 let nu13 = self.nu13;
639 let nu23 = self.nu23;
640 let delta = 1.0 - nu12 * nu21 - nu23 * nu32 - nu13 * nu31 - 2.0 * nu21 * nu32 * nu13;
641 let e1 = self.e1;
642 let e2 = self.e2;
643 let e3 = self.e3;
644 let mut d = [[0.0_f64; 6]; 6];
645 d[0][0] = (1.0 - nu23 * nu32) * e1 / delta;
646 d[1][1] = (1.0 - nu13 * nu31) * e2 / delta;
647 d[2][2] = (1.0 - nu12 * nu21) * e3 / delta;
648 d[0][1] = (nu21 + nu31 * nu23) * e1 / delta;
649 d[1][0] = d[0][1];
650 d[0][2] = (nu31 + nu21 * nu32) * e1 / delta;
651 d[2][0] = d[0][2];
652 d[1][2] = (nu32 + nu12 * nu31) * e2 / delta;
653 d[2][1] = d[1][2];
654 d[3][3] = self.g12;
655 d[4][4] = self.g23;
656 d[5][5] = self.g13;
657 d
658 }
659 pub fn check_symmetry(&self) -> bool {
661 let tol = 1.0e-10;
662 let nu21 = self.nu12 * self.e2 / self.e1;
663 let nu31 = self.nu13 * self.e3 / self.e1;
664 let nu32 = self.nu23 * self.e3 / self.e2;
665 (self.nu12 / self.e1 - nu21 / self.e2).abs() < tol
666 && (self.nu13 / self.e1 - nu31 / self.e3).abs() < tol
667 && (self.nu23 / self.e2 - nu32 / self.e3).abs() < tol
668 }
669 pub fn bulk_modulus_longitudinal(&self) -> f64 {
673 let e_avg = (self.e1 + self.e2 + self.e3) / 3.0;
674 let nu_avg = (self.nu12 + self.nu23 + self.nu13) / 3.0;
675 e_avg / (3.0 * (1.0 - 2.0 * nu_avg))
676 }
677}
678#[derive(Debug, Clone, Copy)]
683pub struct FailureEnvelope {
684 pub xt: f64,
686 pub xc: f64,
688 pub yt: f64,
690 pub yc: f64,
692}
693impl FailureEnvelope {
694 pub fn max_stress(xt: f64, xc: f64, yt: f64, yc: f64) -> Self {
696 Self { xt, xc, yt, yc }
697 }
698 pub fn failure_index_biaxial(&self, sigma11: f64, sigma22: f64) -> f64 {
703 let fi1 = if sigma11 >= 0.0 {
704 sigma11 / self.xt
705 } else {
706 sigma11.abs() / self.xc.abs()
707 };
708 let fi2 = if sigma22 >= 0.0 {
709 sigma22 / self.yt
710 } else {
711 sigma22.abs() / self.yc.abs()
712 };
713 fi1.max(fi2)
714 }
715 pub fn tsai_wu_index(&self, sigma11: f64, sigma22: f64) -> f64 {
721 let f1 = 1.0 / self.xt + 1.0 / self.xc;
722 let f2 = 1.0 / self.yt + 1.0 / self.yc;
723 let f11 = -1.0 / (self.xt * self.xc);
724 let f22 = -1.0 / (self.yt * self.yc);
725 let f12 = -0.5 * (f11 * f22).abs().sqrt();
726 f1 * sigma11
727 + f2 * sigma22
728 + f11 * sigma11 * sigma11
729 + f22 * sigma22 * sigma22
730 + 2.0 * f12 * sigma11 * sigma22
731 }
732 pub fn is_inside(&self, sigma11: f64, sigma22: f64) -> bool {
734 self.failure_index_biaxial(sigma11, sigma22) < 1.0
735 }
736}
737#[derive(Debug, Clone, Copy)]
739pub struct DiffusionTensor {
740 pub d: [[f64; 3]; 3],
742}
743impl DiffusionTensor {
744 pub fn new(d: [[f64; 3]; 3]) -> Self {
746 Self { d }
747 }
748 pub fn orthotropic(dx: f64, dy: f64, dz: f64) -> Self {
750 let mut d = [[0.0f64; 3]; 3];
751 d[0][0] = dx;
752 d[1][1] = dy;
753 d[2][2] = dz;
754 Self::new(d)
755 }
756 #[allow(clippy::needless_range_loop)]
758 pub fn diffusive_flux(&self, grad_c: [f64; 3]) -> [f64; 3] {
759 let mut j = [0.0f64; 3];
760 for i in 0..3 {
761 for j_idx in 0..3 {
762 j[i] -= self.d[i][j_idx] * grad_c[j_idx];
763 }
764 }
765 j
766 }
767 pub fn msd_along(&self, n: [f64; 3], t: f64) -> f64 {
771 let d_eff: f64 = (0..3)
772 .flat_map(|i| (0..3).map(move |j| n[i] * self.d[i][j] * n[j]))
773 .sum();
774 2.0 * d_eff * t
775 }
776 pub fn mean_diffusivity(&self) -> f64 {
778 (self.d[0][0] + self.d[1][1] + self.d[2][2]) / 3.0
779 }
780}
781#[derive(Debug, Clone, Copy)]
789pub struct HillYieldCriterion {
790 pub f: f64,
792 pub g: f64,
794 pub h: f64,
796 pub l: f64,
798 pub m: f64,
800 pub n: f64,
802}
803impl HillYieldCriterion {
804 pub fn new(f: f64, g: f64, h: f64, l: f64, m: f64, n: f64) -> Self {
806 Self { f, g, h, l, m, n }
807 }
808 pub fn isotropic(sigma_y: f64) -> Self {
813 let sy2 = sigma_y * sigma_y;
814 Self::new(
815 0.5 / sy2,
816 0.5 / sy2,
817 0.5 / sy2,
818 1.5 / sy2,
819 1.5 / sy2,
820 1.5 / sy2,
821 )
822 }
823 pub fn from_r_values(r11: f64, r22: f64, r33: f64) -> Self {
830 let h = r11 / (1.0 + r11);
831 let g = 1.0 / (1.0 + r11);
832 let f = g * r22 / r11;
833 let n = (r11 + r22) * (1.0 + 2.0 * r33) / (2.0 * r33 * (1.0 + r11));
834 let l = 1.5;
835 let m = 1.5;
836 Self::new(f, g, h, l, m, n)
837 }
838 pub fn yield_function(&self, sigma: [f64; 6], sigma_y: f64) -> f64 {
843 let [s11, s22, s33, t12, t23, t13] = sigma;
844 let q2 = self.f * (s22 - s33).powi(2)
845 + self.g * (s33 - s11).powi(2)
846 + self.h * (s11 - s22).powi(2)
847 + 2.0 * self.l * t23 * t23
848 + 2.0 * self.m * t13 * t13
849 + 2.0 * self.n * t12 * t12;
850 q2 - sigma_y * sigma_y
851 }
852 pub fn effective_stress(&self, sigma: [f64; 6]) -> f64 {
856 let [s11, s22, s33, t12, t23, t13] = sigma;
857 let q2 = self.f * (s22 - s33).powi(2)
858 + self.g * (s33 - s11).powi(2)
859 + self.h * (s11 - s22).powi(2)
860 + 2.0 * self.l * t23 * t23
861 + 2.0 * self.m * t13 * t13
862 + 2.0 * self.n * t12 * t12;
863 q2.max(0.0).sqrt()
864 }
865 pub fn normal(&self, sigma: [f64; 6]) -> [f64; 6] {
869 let [s11, s22, s33, t12, t23, t13] = sigma;
870 [
871 -2.0 * self.g * (s33 - s11) + 2.0 * self.h * (s11 - s22),
872 2.0 * self.f * (s22 - s33) - 2.0 * self.h * (s11 - s22),
873 -2.0 * self.f * (s22 - s33) + 2.0 * self.g * (s33 - s11),
874 4.0 * self.n * t12,
875 4.0 * self.l * t23,
876 4.0 * self.m * t13,
877 ]
878 }
879}
880#[derive(Debug, Clone, Copy)]
887pub struct HashinFailureCriteria {
888 pub xt: f64,
890 pub xc: f64,
892 pub yt: f64,
894 pub yc: f64,
896 pub s12: f64,
898 pub s23: f64,
900}
901impl HashinFailureCriteria {
902 #[allow(clippy::too_many_arguments)]
904 pub fn new(xt: f64, xc: f64, yt: f64, yc: f64, s12: f64, s23: f64) -> Self {
905 Self {
906 xt,
907 xc,
908 yt,
909 yc,
910 s12,
911 s23,
912 }
913 }
914 pub fn carbon_epoxy_typical() -> Self {
916 Self::new(1480.0e6, 1200.0e6, 50.0e6, 170.0e6, 70.0e6, 40.0e6)
917 }
918 pub fn fibre_tension(&self, sigma11: f64, tau12: f64) -> f64 {
922 (sigma11 / self.xt).powi(2) + (tau12 / self.s12).powi(2)
923 }
924 pub fn fibre_compression(&self, sigma11: f64) -> f64 {
926 (sigma11.abs() / self.xc).powi(2)
927 }
928 pub fn fiber_tension(&self, sigma11: f64, tau12: f64, _tau13: f64) -> f64 {
930 self.fibre_tension(sigma11, tau12)
931 }
932 pub fn fiber_compression(&self, sigma11: f64) -> f64 {
934 self.fibre_compression(sigma11)
935 }
936 pub fn matrix_tension(&self, sigma22: f64, tau12: f64) -> f64 {
938 (sigma22 / self.yt).powi(2) + (tau12 / self.s12).powi(2)
939 }
940 pub fn matrix_compression(&self, sigma22: f64, tau12: f64) -> f64 {
944 let a = (sigma22.abs() / (2.0 * self.s23)).powi(2);
945 let b = (self.yc / (2.0 * self.s23) - 1.0) * sigma22.abs() / self.yc;
946 let c = (tau12 / self.s12).powi(2);
947 a + b + c
948 }
949 pub fn max_failure_index(
953 &self,
954 sigma11: f64,
955 sigma22: f64,
956 tau12: f64,
957 tau23: f64,
958 ) -> (f64, usize) {
959 let _ = tau23;
960 let fi = [
961 if sigma11 >= 0.0 {
962 self.fibre_tension(sigma11, tau12)
963 } else {
964 0.0
965 },
966 if sigma11 < 0.0 {
967 self.fibre_compression(sigma11)
968 } else {
969 0.0
970 },
971 if sigma22 >= 0.0 {
972 self.matrix_tension(sigma22, tau12)
973 } else {
974 0.0
975 },
976 if sigma22 < 0.0 {
977 self.matrix_compression(sigma22, tau12)
978 } else {
979 0.0
980 },
981 ];
982 let max_idx = fi
983 .iter()
984 .enumerate()
985 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
986 .map(|(i, _)| i)
987 .unwrap_or(0);
988 (fi[max_idx], max_idx)
989 }
990}
991#[derive(Debug, Clone, Copy)]
1000pub struct PuckFailureCriteria {
1001 pub xt: f64,
1003 pub yt: f64,
1005 pub s21: f64,
1007 pub p_t: f64,
1009 pub p_c: f64,
1011}
1012impl PuckFailureCriteria {
1013 pub fn new(xt: f64, yt: f64, s21: f64, p_t: f64, p_c: f64) -> Self {
1015 Self {
1016 xt,
1017 yt,
1018 s21,
1019 p_t,
1020 p_c,
1021 }
1022 }
1023 pub fn carbon_epoxy_typical() -> Self {
1025 Self::new(1480.0e6, 50.0e6, 70.0e6, 0.27, 0.32)
1026 }
1027 pub fn fibre_failure_index(&self, sigma11: f64, xt: f64) -> f64 {
1029 sigma11.abs() / xt
1030 }
1031 pub fn inter_fibre_failure_index(&self, sigma22: f64, tau21: f64, _tau23: f64) -> f64 {
1035 if sigma22 >= 0.0 {
1036 let a = (tau21 / self.s21).powi(2);
1037 let b = (1.0 - self.p_t * self.yt / self.s21).powi(2) * (sigma22 / self.yt).powi(2);
1038 let fi = (a + b).sqrt() + self.p_t * sigma22 / self.s21;
1039 fi.max(0.0)
1040 } else {
1041 let a = (tau21 / self.s21).powi(2);
1042 let b = (self.p_c * sigma22.abs() / self.s21).powi(2);
1043 (a + b).sqrt() + self.p_c * sigma22.abs() / self.s21
1044 }
1045 }
1046 pub fn fiber_failure_tension(&self, sigma1: f64, _e_f1: f64, sigma2: f64, _e_f2: f64) -> f64 {
1050 if sigma1 <= 0.0 {
1051 return 0.0;
1052 }
1053 let correction = 1.0 + sigma2 / self.yt * 0.01;
1054 (sigma1 / self.xt) * correction
1055 }
1056 pub fn fiber_failure_compression(&self, sigma1: f64) -> f64 {
1058 if sigma1 >= 0.0 {
1059 return 0.0;
1060 }
1061 (-sigma1) / self.xt
1064 }
1065 pub fn inter_fiber_failure_mode_a(
1067 &self,
1068 sigma2: f64,
1069 tau12: f64,
1070 p_tt: f64,
1071 _p_ct: f64,
1072 ) -> f64 {
1073 if sigma2 < 0.0 {
1074 return 0.0;
1075 }
1076 let term_tau = (tau12 / self.s21).powi(2);
1077 let factor = 1.0 - p_tt * self.yt / self.s21;
1078 let term_sig = (factor * sigma2 / self.yt).powi(2);
1079 (term_tau + term_sig).sqrt() + p_tt * sigma2 / self.s21
1080 }
1081}
1082#[derive(Debug, Clone, Copy)]
1084pub struct SymmetryOperation {
1085 pub matrix: [[f64; 3]; 3],
1087}
1088impl SymmetryOperation {
1089 pub fn new(matrix: [[f64; 3]; 3]) -> Self {
1091 Self { matrix }
1092 }
1093 pub fn identity() -> Self {
1095 Self::new([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
1096 }
1097 pub fn c2_z() -> Self {
1099 Self::new([[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]])
1100 }
1101 pub fn inversion() -> Self {
1103 Self::new([[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]])
1104 }
1105 pub fn mirror_z() -> Self {
1107 Self::new([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]])
1108 }
1109 #[allow(clippy::needless_range_loop)]
1111 pub fn apply(&self, v: [f64; 3]) -> [f64; 3] {
1112 let mut out = [0.0f64; 3];
1113 for i in 0..3 {
1114 for j in 0..3 {
1115 out[i] += self.matrix[i][j] * v[j];
1116 }
1117 }
1118 out
1119 }
1120 #[allow(clippy::needless_range_loop)]
1122 pub fn compose(&self, other: &SymmetryOperation) -> SymmetryOperation {
1123 let mut m = [[0.0f64; 3]; 3];
1124 for i in 0..3 {
1125 for j in 0..3 {
1126 for k in 0..3 {
1127 m[i][j] += self.matrix[i][k] * other.matrix[k][j];
1128 }
1129 }
1130 }
1131 SymmetryOperation::new(m)
1132 }
1133 pub fn determinant(&self) -> f64 {
1135 let m = &self.matrix;
1136 m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
1137 - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
1138 + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
1139 }
1140 pub fn is_proper_rotation(&self) -> bool {
1142 (self.determinant() - 1.0).abs() < 1e-10
1143 }
1144}