#[allow(unused_imports)]
use super::functions::*;
#[derive(Debug, Clone, Copy)]
pub struct Ply {
pub e1: f64,
pub e2: f64,
pub nu12: f64,
pub g12: f64,
pub thickness: f64,
pub angle_deg: f64,
}
impl Ply {
#[allow(clippy::too_many_arguments)]
pub fn new(e1: f64, e2: f64, nu12: f64, g12: f64, thickness: f64, angle_deg: f64) -> Self {
Self {
e1,
e2,
nu12,
g12,
thickness,
angle_deg,
}
}
pub fn nu21(&self) -> f64 {
self.nu12 * self.e2 / self.e1
}
pub fn reduced_stiffness(&self) -> [f64; 4] {
let nu21 = self.nu12 * self.e2 / self.e1;
let denom = 1.0 - self.nu12 * nu21;
let q11 = self.e1 / denom;
let q22 = self.e2 / denom;
let q12 = self.nu12 * self.e2 / denom;
let q66 = self.g12;
[q11, q22, q12, q66]
}
pub fn transformed_stiffness(&self) -> [[f64; 3]; 3] {
let [q11, q22, q12, q66] = self.reduced_stiffness();
let theta = self.angle_deg.to_radians();
let m = theta.cos();
let n = theta.sin();
let m2 = m * m;
let n2 = n * n;
let m4 = m2 * m2;
let n4 = n2 * n2;
let m2n2 = m2 * n2;
let m3n = m2 * m * n;
let mn3 = m * n2 * n;
let qb11 = q11 * m4 + 2.0 * (q12 + 2.0 * q66) * m2n2 + q22 * n4;
let qb22 = q11 * n4 + 2.0 * (q12 + 2.0 * q66) * m2n2 + q22 * m4;
let qb12 = (q11 + q22 - 4.0 * q66) * m2n2 + q12 * (m4 + n4);
let qb66 = (q11 + q22 - 2.0 * q12 - 2.0 * q66) * m2n2 + q66 * (m4 + n4);
let qb16 = (q11 - q12 - 2.0 * q66) * m3n - (q22 - q12 - 2.0 * q66) * mn3;
let qb26 = (q11 - q12 - 2.0 * q66) * mn3 - (q22 - q12 - 2.0 * q66) * m3n;
[[qb11, qb12, qb16], [qb12, qb22, qb26], [qb16, qb26, qb66]]
}
}
#[derive(Debug, Clone, Copy)]
pub struct TransverselyIsotropic {
pub ea: f64,
pub et: f64,
pub nua: f64,
pub nut: f64,
pub ga: f64,
}
impl TransverselyIsotropic {
pub fn new(ea: f64, et: f64, nua: f64, nut: f64, ga: f64) -> Self {
Self {
ea,
et,
nua,
nut,
ga,
}
}
pub fn gt(&self) -> f64 {
self.et / (2.0 * (1.0 + self.nut))
}
pub fn to_orthotropic(&self) -> OrthotropicMaterial {
OrthotropicMaterial::new(
self.ea,
self.et,
self.et,
self.nua,
self.nut,
self.nua,
self.ga,
self.gt(),
self.ga,
)
}
pub fn constitutive_matrix(&self) -> [[f64; 6]; 6] {
self.to_orthotropic().constitutive_matrix()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ThermalConductivityTensor {
pub kappa: [[f64; 3]; 3],
}
impl ThermalConductivityTensor {
pub fn new(kappa: [[f64; 3]; 3]) -> Self {
Self { kappa }
}
pub fn isotropic(k: f64) -> Self {
Self::orthotropic(k, k, k)
}
pub fn orthotropic(kx: f64, ky: f64, kz: f64) -> Self {
let mut k = [[0.0_f64; 3]; 3];
k[0][0] = kx;
k[1][1] = ky;
k[2][2] = kz;
Self::new(k)
}
#[allow(clippy::needless_range_loop)]
pub fn heat_flux(&self, grad_t: [f64; 3]) -> [f64; 3] {
let mut q = [0.0f64; 3];
for i in 0..3 {
for j in 0..3 {
q[i] -= self.kappa[i][j] * grad_t[j];
}
}
q
}
#[allow(clippy::needless_range_loop)]
pub fn effective_conductivity(&self, n: [f64; 3]) -> f64 {
let mut kn = [0.0f64; 3];
for i in 0..3 {
for j in 0..3 {
kn[i] += self.kappa[i][j] * n[j];
}
}
n[0] * kn[0] + n[1] * kn[1] + n[2] * kn[2]
}
pub fn mean_conductivity(&self) -> f64 {
(self.kappa[0][0] + self.kappa[1][1] + self.kappa[2][2]) / 3.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct LaRCFailureCriteria {
pub xt: f64,
pub xc: f64,
pub yis: f64,
pub sis: f64,
pub phi0: f64,
pub eta: f64,
}
impl LaRCFailureCriteria {
#[allow(clippy::too_many_arguments)]
pub fn new(xt: f64, xc: f64, yis: f64, sis: f64, phi0: f64, eta: f64) -> Self {
Self {
xt,
xc,
yis,
sis,
phi0,
eta,
}
}
pub fn default_carbon_epoxy() -> Self {
Self::new(2560.0e6, 1590.0e6, 160.0e6, 130.0e6, 0.015, 0.43)
}
pub fn fibre_kinking_index(&self, sigma11: f64, sigma22: f64, tau12: f64) -> f64 {
let _ = sigma22;
let phi = self.phi0;
let sigma_m = sigma11 * phi.sin().powi(2) + tau12 * 2.0 * phi.sin() * phi.cos();
let tau_m = (sigma11 - 0.0) * phi.sin() * phi.cos()
+ tau12 * (phi.cos().powi(2) - phi.sin().powi(2));
let denominator = (self.sis - self.eta * sigma_m).max(1.0);
tau_m.abs() / denominator
}
pub fn matrix_cracking_index(&self, sigma22: f64, tau12: f64, tau23: f64) -> f64 {
let _ = tau23;
let a = (tau12 / self.sis).powi(2);
let b = if sigma22 > 0.0 {
(sigma22 / self.yis).powi(2)
} else {
0.0
};
(a + b).sqrt()
}
}
#[derive(Debug, Clone, Copy)]
pub struct BraidedComposite {
pub vf: f64,
pub e_fibre: f64,
pub e_matrix: f64,
pub nu_fibre: f64,
pub nu_matrix: f64,
pub braid_angle_deg: f64,
}
impl BraidedComposite {
pub fn triaxial_braid(
vf: f64,
e_fibre: f64,
e_matrix: f64,
nu_fibre: f64,
nu_matrix: f64,
braid_angle_deg: f64,
) -> Self {
Self {
vf,
e_fibre,
e_matrix,
nu_fibre,
nu_matrix,
braid_angle_deg,
}
}
fn vm(&self) -> f64 {
1.0 - self.vf
}
fn e_axial(&self) -> f64 {
self.vf * self.e_fibre + self.vm() * self.e_matrix
}
fn e_transverse(&self) -> f64 {
1.0 / (self.vf / self.e_fibre + self.vm() / self.e_matrix)
}
fn nu12(&self) -> f64 {
self.vf * self.nu_fibre + self.vm() * self.nu_matrix
}
fn g12(&self) -> f64 {
let gf = self.e_fibre / (2.0 * (1.0 + self.nu_fibre));
let gm = self.e_matrix / (2.0 * (1.0 + self.nu_matrix));
1.0 / (self.vf / gf + self.vm() / gm)
}
pub fn in_plane_modulus(&self) -> f64 {
let theta = self.braid_angle_deg.to_radians();
let m = theta.cos();
let n = theta.sin();
let m2 = m * m;
let n2 = n * n;
let m4 = m2 * m2;
let n4 = n2 * n2;
let m2n2 = m2 * n2;
let e1 = self.e_axial();
let e2 = self.e_transverse();
let nu12 = self.nu12();
let nu21 = nu12 * e2 / e1;
let g12 = self.g12();
let denom = 1.0 - nu12 * nu21;
let q11 = e1 / denom;
let q22 = e2 / denom;
let q12 = nu12 * e2 / denom;
let q66 = g12;
let qb11_bias = q11 * m4 + 2.0 * (q12 + 2.0 * q66) * m2n2 + q22 * n4;
let weight_axial = 1.0 / 3.0;
let weight_bias = 2.0 / 3.0;
weight_axial * q11 + weight_bias * qb11_bias
}
pub fn effective_shear_modulus(&self) -> f64 {
let theta = self.braid_angle_deg.to_radians();
let m = theta.cos();
let n = theta.sin();
let m2 = m * m;
let n2 = n * n;
let m4 = m2 * m2;
let n4 = n2 * n2;
let m2n2 = m2 * n2;
let e1 = self.e_axial();
let e2 = self.e_transverse();
let nu12 = self.nu12();
let nu21 = nu12 * e2 / e1;
let g12 = self.g12();
let denom = 1.0 - nu12 * nu21;
let q11 = e1 / denom;
let q22 = e2 / denom;
let q12 = nu12 * e2 / denom;
let q66 = g12;
let qb66_bias = (q11 + q22 - 2.0 * q12 - 2.0 * q66) * m2n2 + q66 * (m4 + n4);
(1.0 / 3.0) * q66 + (2.0 / 3.0) * qb66_bias
}
}
#[derive(Debug, Clone, Copy)]
pub struct MonoclinicMaterial {
pub s11: f64,
pub s12: f64,
pub s13: f64,
pub s16: f64,
pub s22: f64,
pub s23: f64,
pub s26: f64,
pub s33: f64,
pub s36: f64,
pub s44: f64,
pub s45: f64,
pub s55: f64,
pub s66: f64,
}
impl MonoclinicMaterial {
#[allow(clippy::too_many_arguments)]
pub fn new(
s11: f64,
s12: f64,
s13: f64,
s16: f64,
s22: f64,
s23: f64,
s26: f64,
s33: f64,
s36: f64,
s44: f64,
s45: f64,
s55: f64,
s66: f64,
) -> Self {
Self {
s11,
s12,
s13,
s16,
s22,
s23,
s26,
s33,
s36,
s44,
s45,
s55,
s66,
}
}
pub fn e1(&self) -> f64 {
1.0 / self.s11
}
pub fn e2(&self) -> f64 {
1.0 / self.s22
}
pub fn is_physically_valid(&self) -> bool {
self.s11 > 0.0 && self.s22 > 0.0 && self.s33 > 0.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct WovenLamina {
pub e11: f64,
pub e22: f64,
pub nu12: f64,
pub g12: f64,
pub e33: f64,
pub g13: f64,
pub g23: f64,
}
impl WovenLamina {
pub fn balanced_plain_weave(
fiber_vol: f64,
e_fiber: f64,
e_matrix: f64,
nu_fiber: f64,
nu_matrix: f64,
) -> Self {
let vm = 1.0 - fiber_vol;
let vf = fiber_vol;
let e11 = vf * e_fiber + vm * e_matrix;
let e22 = e11;
let nu12 = vf * nu_fiber + vm * nu_matrix;
let g_fiber = e_fiber / (2.0 * (1.0 + nu_fiber));
let g_matrix = e_matrix / (2.0 * (1.0 + nu_matrix));
let g12 = 1.0 / (vf / g_fiber + vm / g_matrix);
let e33 = 1.0 / (vf / e_fiber + vm / e_matrix);
let g13 = 1.0 / (vf / g_fiber + vm / g_matrix);
let g23 = g13;
Self {
e11,
e22,
nu12,
g12,
e33,
g13,
g23,
}
}
pub fn constitutive_matrix_2d(&self) -> [[f64; 3]; 3] {
let nu21 = self.nu12 * self.e22 / self.e11;
let denom = 1.0 - self.nu12 * nu21;
let mut q = [[0.0_f64; 3]; 3];
q[0][0] = self.e11 / denom;
q[1][1] = self.e22 / denom;
q[0][1] = self.nu12 * self.e22 / denom;
q[1][0] = q[0][1];
q[2][2] = self.g12;
q
}
}
#[derive(Debug, Clone, Copy)]
pub struct OrthotropicMaterial {
pub e1: f64,
pub e2: f64,
pub e3: f64,
pub nu12: f64,
pub nu23: f64,
pub nu13: f64,
pub g12: f64,
pub g23: f64,
pub g13: f64,
}
impl OrthotropicMaterial {
#[allow(clippy::too_many_arguments)]
pub fn new(
e1: f64,
e2: f64,
e3: f64,
nu12: f64,
nu23: f64,
nu13: f64,
g12: f64,
g23: f64,
g13: f64,
) -> Self {
Self {
e1,
e2,
e3,
nu12,
nu23,
nu13,
g12,
g23,
g13,
}
}
pub fn carbon_fiber_epoxy() -> Self {
Self::new(140.0e9, 10.0e9, 10.0e9, 0.3, 0.4, 0.3, 5.0e9, 3.5e9, 5.0e9)
}
pub fn douglas_fir() -> Self {
Self::new(
13.5e9, 0.9e9, 0.75e9, 0.292, 0.37, 0.374, 0.88e9, 0.06e9, 1.07e9,
)
}
pub fn compliance_matrix(&self) -> [[f64; 6]; 6] {
let nu21 = self.nu12 * self.e2 / self.e1;
let nu31 = self.nu13 * self.e3 / self.e1;
let nu32 = self.nu23 * self.e3 / self.e2;
let mut s = [[0.0_f64; 6]; 6];
s[0][0] = 1.0 / self.e1;
s[1][1] = 1.0 / self.e2;
s[2][2] = 1.0 / self.e3;
s[0][1] = -nu21 / self.e2;
s[1][0] = -nu21 / self.e2;
s[0][2] = -nu31 / self.e3;
s[2][0] = -nu31 / self.e3;
s[1][2] = -nu32 / self.e3;
s[2][1] = -nu32 / self.e3;
s[3][3] = 1.0 / self.g12;
s[4][4] = 1.0 / self.g23;
s[5][5] = 1.0 / self.g13;
s
}
pub fn constitutive_matrix(&self) -> [[f64; 6]; 6] {
let nu21 = self.nu12 * self.e2 / self.e1;
let nu31 = self.nu13 * self.e3 / self.e1;
let nu32 = self.nu23 * self.e3 / self.e2;
let nu12 = self.nu12;
let nu13 = self.nu13;
let nu23 = self.nu23;
let delta = 1.0 - nu12 * nu21 - nu23 * nu32 - nu13 * nu31 - 2.0 * nu21 * nu32 * nu13;
let e1 = self.e1;
let e2 = self.e2;
let e3 = self.e3;
let mut d = [[0.0_f64; 6]; 6];
d[0][0] = (1.0 - nu23 * nu32) * e1 / delta;
d[1][1] = (1.0 - nu13 * nu31) * e2 / delta;
d[2][2] = (1.0 - nu12 * nu21) * e3 / delta;
d[0][1] = (nu21 + nu31 * nu23) * e1 / delta;
d[1][0] = d[0][1];
d[0][2] = (nu31 + nu21 * nu32) * e1 / delta;
d[2][0] = d[0][2];
d[1][2] = (nu32 + nu12 * nu31) * e2 / delta;
d[2][1] = d[1][2];
d[3][3] = self.g12;
d[4][4] = self.g23;
d[5][5] = self.g13;
d
}
pub fn check_symmetry(&self) -> bool {
let tol = 1.0e-10;
let nu21 = self.nu12 * self.e2 / self.e1;
let nu31 = self.nu13 * self.e3 / self.e1;
let nu32 = self.nu23 * self.e3 / self.e2;
(self.nu12 / self.e1 - nu21 / self.e2).abs() < tol
&& (self.nu13 / self.e1 - nu31 / self.e3).abs() < tol
&& (self.nu23 / self.e2 - nu32 / self.e3).abs() < tol
}
pub fn bulk_modulus_longitudinal(&self) -> f64 {
let e_avg = (self.e1 + self.e2 + self.e3) / 3.0;
let nu_avg = (self.nu12 + self.nu23 + self.nu13) / 3.0;
e_avg / (3.0 * (1.0 - 2.0 * nu_avg))
}
}
#[derive(Debug, Clone, Copy)]
pub struct FailureEnvelope {
pub xt: f64,
pub xc: f64,
pub yt: f64,
pub yc: f64,
}
impl FailureEnvelope {
pub fn max_stress(xt: f64, xc: f64, yt: f64, yc: f64) -> Self {
Self { xt, xc, yt, yc }
}
pub fn failure_index_biaxial(&self, sigma11: f64, sigma22: f64) -> f64 {
let fi1 = if sigma11 >= 0.0 {
sigma11 / self.xt
} else {
sigma11.abs() / self.xc.abs()
};
let fi2 = if sigma22 >= 0.0 {
sigma22 / self.yt
} else {
sigma22.abs() / self.yc.abs()
};
fi1.max(fi2)
}
pub fn tsai_wu_index(&self, sigma11: f64, sigma22: f64) -> f64 {
let f1 = 1.0 / self.xt + 1.0 / self.xc;
let f2 = 1.0 / self.yt + 1.0 / self.yc;
let f11 = -1.0 / (self.xt * self.xc);
let f22 = -1.0 / (self.yt * self.yc);
let f12 = -0.5 * (f11 * f22).abs().sqrt();
f1 * sigma11
+ f2 * sigma22
+ f11 * sigma11 * sigma11
+ f22 * sigma22 * sigma22
+ 2.0 * f12 * sigma11 * sigma22
}
pub fn is_inside(&self, sigma11: f64, sigma22: f64) -> bool {
self.failure_index_biaxial(sigma11, sigma22) < 1.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct DiffusionTensor {
pub d: [[f64; 3]; 3],
}
impl DiffusionTensor {
pub fn new(d: [[f64; 3]; 3]) -> Self {
Self { d }
}
pub fn orthotropic(dx: f64, dy: f64, dz: f64) -> Self {
let mut d = [[0.0f64; 3]; 3];
d[0][0] = dx;
d[1][1] = dy;
d[2][2] = dz;
Self::new(d)
}
#[allow(clippy::needless_range_loop)]
pub fn diffusive_flux(&self, grad_c: [f64; 3]) -> [f64; 3] {
let mut j = [0.0f64; 3];
for i in 0..3 {
for j_idx in 0..3 {
j[i] -= self.d[i][j_idx] * grad_c[j_idx];
}
}
j
}
pub fn msd_along(&self, n: [f64; 3], t: f64) -> f64 {
let d_eff: f64 = (0..3)
.flat_map(|i| (0..3).map(move |j| n[i] * self.d[i][j] * n[j]))
.sum();
2.0 * d_eff * t
}
pub fn mean_diffusivity(&self) -> f64 {
(self.d[0][0] + self.d[1][1] + self.d[2][2]) / 3.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct HillYieldCriterion {
pub f: f64,
pub g: f64,
pub h: f64,
pub l: f64,
pub m: f64,
pub n: f64,
}
impl HillYieldCriterion {
pub fn new(f: f64, g: f64, h: f64, l: f64, m: f64, n: f64) -> Self {
Self { f, g, h, l, m, n }
}
pub fn isotropic(sigma_y: f64) -> Self {
let sy2 = sigma_y * sigma_y;
Self::new(
0.5 / sy2,
0.5 / sy2,
0.5 / sy2,
1.5 / sy2,
1.5 / sy2,
1.5 / sy2,
)
}
pub fn from_r_values(r11: f64, r22: f64, r33: f64) -> Self {
let h = r11 / (1.0 + r11);
let g = 1.0 / (1.0 + r11);
let f = g * r22 / r11;
let n = (r11 + r22) * (1.0 + 2.0 * r33) / (2.0 * r33 * (1.0 + r11));
let l = 1.5;
let m = 1.5;
Self::new(f, g, h, l, m, n)
}
pub fn yield_function(&self, sigma: [f64; 6], sigma_y: f64) -> f64 {
let [s11, s22, s33, t12, t23, t13] = sigma;
let q2 = self.f * (s22 - s33).powi(2)
+ self.g * (s33 - s11).powi(2)
+ self.h * (s11 - s22).powi(2)
+ 2.0 * self.l * t23 * t23
+ 2.0 * self.m * t13 * t13
+ 2.0 * self.n * t12 * t12;
q2 - sigma_y * sigma_y
}
pub fn effective_stress(&self, sigma: [f64; 6]) -> f64 {
let [s11, s22, s33, t12, t23, t13] = sigma;
let q2 = self.f * (s22 - s33).powi(2)
+ self.g * (s33 - s11).powi(2)
+ self.h * (s11 - s22).powi(2)
+ 2.0 * self.l * t23 * t23
+ 2.0 * self.m * t13 * t13
+ 2.0 * self.n * t12 * t12;
q2.max(0.0).sqrt()
}
pub fn normal(&self, sigma: [f64; 6]) -> [f64; 6] {
let [s11, s22, s33, t12, t23, t13] = sigma;
[
-2.0 * self.g * (s33 - s11) + 2.0 * self.h * (s11 - s22),
2.0 * self.f * (s22 - s33) - 2.0 * self.h * (s11 - s22),
-2.0 * self.f * (s22 - s33) + 2.0 * self.g * (s33 - s11),
4.0 * self.n * t12,
4.0 * self.l * t23,
4.0 * self.m * t13,
]
}
}
#[derive(Debug, Clone, Copy)]
pub struct HashinFailureCriteria {
pub xt: f64,
pub xc: f64,
pub yt: f64,
pub yc: f64,
pub s12: f64,
pub s23: f64,
}
impl HashinFailureCriteria {
#[allow(clippy::too_many_arguments)]
pub fn new(xt: f64, xc: f64, yt: f64, yc: f64, s12: f64, s23: f64) -> Self {
Self {
xt,
xc,
yt,
yc,
s12,
s23,
}
}
pub fn carbon_epoxy_typical() -> Self {
Self::new(1480.0e6, 1200.0e6, 50.0e6, 170.0e6, 70.0e6, 40.0e6)
}
pub fn fibre_tension(&self, sigma11: f64, tau12: f64) -> f64 {
(sigma11 / self.xt).powi(2) + (tau12 / self.s12).powi(2)
}
pub fn fibre_compression(&self, sigma11: f64) -> f64 {
(sigma11.abs() / self.xc).powi(2)
}
pub fn fiber_tension(&self, sigma11: f64, tau12: f64, _tau13: f64) -> f64 {
self.fibre_tension(sigma11, tau12)
}
pub fn fiber_compression(&self, sigma11: f64) -> f64 {
self.fibre_compression(sigma11)
}
pub fn matrix_tension(&self, sigma22: f64, tau12: f64) -> f64 {
(sigma22 / self.yt).powi(2) + (tau12 / self.s12).powi(2)
}
pub fn matrix_compression(&self, sigma22: f64, tau12: f64) -> f64 {
let a = (sigma22.abs() / (2.0 * self.s23)).powi(2);
let b = (self.yc / (2.0 * self.s23) - 1.0) * sigma22.abs() / self.yc;
let c = (tau12 / self.s12).powi(2);
a + b + c
}
pub fn max_failure_index(
&self,
sigma11: f64,
sigma22: f64,
tau12: f64,
tau23: f64,
) -> (f64, usize) {
let _ = tau23;
let fi = [
if sigma11 >= 0.0 {
self.fibre_tension(sigma11, tau12)
} else {
0.0
},
if sigma11 < 0.0 {
self.fibre_compression(sigma11)
} else {
0.0
},
if sigma22 >= 0.0 {
self.matrix_tension(sigma22, tau12)
} else {
0.0
},
if sigma22 < 0.0 {
self.matrix_compression(sigma22, tau12)
} else {
0.0
},
];
let max_idx = fi
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
.map(|(i, _)| i)
.unwrap_or(0);
(fi[max_idx], max_idx)
}
}
#[derive(Debug, Clone, Copy)]
pub struct PuckFailureCriteria {
pub xt: f64,
pub yt: f64,
pub s21: f64,
pub p_t: f64,
pub p_c: f64,
}
impl PuckFailureCriteria {
pub fn new(xt: f64, yt: f64, s21: f64, p_t: f64, p_c: f64) -> Self {
Self {
xt,
yt,
s21,
p_t,
p_c,
}
}
pub fn carbon_epoxy_typical() -> Self {
Self::new(1480.0e6, 50.0e6, 70.0e6, 0.27, 0.32)
}
pub fn fibre_failure_index(&self, sigma11: f64, xt: f64) -> f64 {
sigma11.abs() / xt
}
pub fn inter_fibre_failure_index(&self, sigma22: f64, tau21: f64, _tau23: f64) -> f64 {
if sigma22 >= 0.0 {
let a = (tau21 / self.s21).powi(2);
let b = (1.0 - self.p_t * self.yt / self.s21).powi(2) * (sigma22 / self.yt).powi(2);
let fi = (a + b).sqrt() + self.p_t * sigma22 / self.s21;
fi.max(0.0)
} else {
let a = (tau21 / self.s21).powi(2);
let b = (self.p_c * sigma22.abs() / self.s21).powi(2);
(a + b).sqrt() + self.p_c * sigma22.abs() / self.s21
}
}
pub fn fiber_failure_tension(&self, sigma1: f64, _e_f1: f64, sigma2: f64, _e_f2: f64) -> f64 {
if sigma1 <= 0.0 {
return 0.0;
}
let correction = 1.0 + sigma2 / self.yt * 0.01;
(sigma1 / self.xt) * correction
}
pub fn fiber_failure_compression(&self, sigma1: f64) -> f64 {
if sigma1 >= 0.0 {
return 0.0;
}
(-sigma1) / self.xt
}
pub fn inter_fiber_failure_mode_a(
&self,
sigma2: f64,
tau12: f64,
p_tt: f64,
_p_ct: f64,
) -> f64 {
if sigma2 < 0.0 {
return 0.0;
}
let term_tau = (tau12 / self.s21).powi(2);
let factor = 1.0 - p_tt * self.yt / self.s21;
let term_sig = (factor * sigma2 / self.yt).powi(2);
(term_tau + term_sig).sqrt() + p_tt * sigma2 / self.s21
}
}
#[derive(Debug, Clone, Copy)]
pub struct SymmetryOperation {
pub matrix: [[f64; 3]; 3],
}
impl SymmetryOperation {
pub fn new(matrix: [[f64; 3]; 3]) -> Self {
Self { matrix }
}
pub fn identity() -> Self {
Self::new([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
}
pub fn c2_z() -> Self {
Self::new([[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, 1.0]])
}
pub fn inversion() -> Self {
Self::new([[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0], [0.0, 0.0, -1.0]])
}
pub fn mirror_z() -> Self {
Self::new([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]])
}
#[allow(clippy::needless_range_loop)]
pub fn apply(&self, v: [f64; 3]) -> [f64; 3] {
let mut out = [0.0f64; 3];
for i in 0..3 {
for j in 0..3 {
out[i] += self.matrix[i][j] * v[j];
}
}
out
}
#[allow(clippy::needless_range_loop)]
pub fn compose(&self, other: &SymmetryOperation) -> SymmetryOperation {
let mut m = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
m[i][j] += self.matrix[i][k] * other.matrix[k][j];
}
}
}
SymmetryOperation::new(m)
}
pub fn determinant(&self) -> f64 {
let m = &self.matrix;
m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
- m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
+ m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
}
pub fn is_proper_rotation(&self) -> bool {
(self.determinant() - 1.0).abs() < 1e-10
}
}