use std::f64::consts::PI;
pub struct GeometryFactors;
impl GeometryFactors {
pub fn infinite_plate() -> f64 {
1.0
}
pub fn edge_crack() -> f64 {
1.12
}
pub fn penny_crack() -> f64 {
2.0 / PI
}
pub fn finite_width(a: f64, w: f64) -> f64 {
let ratio = a / w;
let poly = 1.0 - 0.025 * ratio * ratio + 0.06 * ratio.powi(4);
let sec_term = (PI * a / (2.0 * w)).cos();
if sec_term.abs() < 1e-30 {
return f64::INFINITY;
}
poly * (1.0 / sec_term).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct JIntegralPoint {
pub stress: [f64; 3],
pub displacement_gradient: [f64; 4],
pub normal: [f64; 2],
pub ds: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SifMode {
ModeI,
ModeII,
ModeIII,
}
#[derive(Debug, Clone)]
pub struct CompactTensionSpec {
pub width: f64,
pub crack_length: f64,
pub thickness: f64,
}
impl CompactTensionSpec {
pub fn new(width: f64, crack_length: f64, thickness: f64) -> Self {
Self {
width,
crack_length,
thickness,
}
}
pub fn a_over_w(&self) -> f64 {
self.crack_length / self.width
}
pub fn geometry_factor(&self) -> f64 {
let x = self.a_over_w();
let poly = 0.886 + 4.64 * x - 13.32 * x * x + 14.72 * x.powi(3) - 5.6 * x.powi(4);
let factor = (2.0 + x) / (1.0 - x).powf(1.5);
factor * poly
}
pub fn k_from_load(&self, load: f64) -> f64 {
let f = self.geometry_factor();
load / (self.thickness * self.width.sqrt()) * f
}
pub fn is_plane_strain_valid(&self, k_ic: f64, yield_stress: f64) -> bool {
let required = 2.5 * (k_ic / yield_stress).powi(2);
self.thickness >= required && self.crack_length >= required
}
}
#[derive(Debug, Clone)]
pub struct DomainPoint {
pub stress: [f64; 3],
pub strain: [f64; 3],
pub displacement_gradient: [f64; 4],
pub q: f64,
pub dq: [f64; 2],
pub area: f64,
}
pub struct CzmBilinear;
impl CzmBilinear {
pub fn normal_traction(p: &CzmBilinearParams, delta_n: f64) -> f64 {
if delta_n <= 0.0 {
return 0.0;
}
let delta_n_0 = p.delta_n_c / 2.0;
if delta_n <= delta_n_0 {
p.t_n_max * delta_n / delta_n_0
} else if delta_n < p.delta_n_c {
p.t_n_max * (p.delta_n_c - delta_n) / (p.delta_n_c - delta_n_0)
} else {
0.0
}
}
pub fn shear_traction(p: &CzmBilinearParams, delta_s: f64) -> f64 {
let abs_s = delta_s.abs();
let delta_s_0 = p.delta_s_c / 2.0;
let t = if abs_s <= delta_s_0 {
p.t_s_max * abs_s / delta_s_0
} else if abs_s < p.delta_s_c {
p.t_s_max * (p.delta_s_c - abs_s) / (p.delta_s_c - delta_s_0)
} else {
0.0
};
if delta_s >= 0.0 { t } else { -t }
}
pub fn effective_opening(delta_n: f64, delta_s: f64) -> f64 {
(delta_n * delta_n + delta_s * delta_s).sqrt()
}
pub fn bk_fracture_energy(g_c_i: f64, g_c_ii: f64, g_i: f64, g_ii: f64, eta: f64) -> f64 {
let g_total = g_i + g_ii;
if g_total == 0.0 {
return g_c_i;
}
let beta = g_ii / g_total;
g_c_i + (g_c_ii - g_c_i) * beta.powf(eta)
}
pub fn damage_variable(delta_eff_max: f64, delta_0: f64, delta_f: f64) -> f64 {
if delta_eff_max <= delta_0 {
return 0.0;
}
((delta_eff_max - delta_0) / (delta_f - delta_0)).min(1.0)
}
pub fn secant_normal_stiffness(k_n: f64, damage: f64) -> f64 {
k_n * (1.0 - damage)
}
pub fn xu_needleman_normal(g_c_i: f64, delta_n_c: f64, delta_n: f64) -> f64 {
if delta_n < 0.0 {
return 0.0;
}
let zeta = delta_n / delta_n_c;
(g_c_i / delta_n_c) * zeta * (-zeta).exp()
}
pub fn crack_opening_work(p: &CzmBilinearParams, steps: usize) -> f64 {
let h = p.delta_n_c / steps as f64;
let mut work = 0.0;
for i in 0..steps {
let d0 = i as f64 * h;
let d1 = d0 + h;
let t0 = Self::normal_traction(p, d0);
let t1 = Self::normal_traction(p, d1);
work += 0.5 * (t0 + t1) * h;
}
work
}
}
pub struct WilliamsExpansion {
pub k_i: f64,
pub t_stress: f64,
pub a3: f64,
pub a4: f64,
}
impl WilliamsExpansion {
pub fn new(k_i: f64, t_stress: f64, a3: f64, a4: f64) -> Self {
Self {
k_i,
t_stress,
a3,
a4,
}
}
pub fn compute_higher_order_terms(&self, r: f64, theta: f64) -> [f64; 3] {
if r < 1e-20 {
return [f64::INFINITY, f64::INFINITY, 0.0];
}
let half = theta / 2.0;
let three_half = 3.0 * theta / 2.0;
let s1 = self.k_i / (2.0 * PI * r).sqrt();
let cos_h = half.cos();
let sin_h = half.sin();
let cos_3h = three_half.cos();
let sin_3h = three_half.sin();
let s1_xx = s1 * cos_h * (1.0 - sin_h * sin_3h);
let s1_yy = s1 * cos_h * (1.0 + sin_h * sin_3h);
let s1_xy = s1 * sin_h * cos_h * cos_3h;
let s2_xx = self.t_stress;
let s2_yy = 0.0;
let s2_xy = 0.0;
let r_sqrt = r.sqrt();
let s3_xx = self.a3 * r_sqrt * (cos_h * (1.0 - 2.0 * sin_h * sin_h));
let s3_yy = self.a3 * r_sqrt * (cos_h * (1.0 + 2.0 * sin_h * sin_h));
let s3_xy = self.a3 * r_sqrt * sin_h * (2.0 * cos_h * cos_h - 1.0);
let x = r * theta.cos();
let y = r * theta.sin();
let s4_xx = self.a4 * (x - y);
let s4_yy = self.a4 * (x + y);
let s4_xy = self.a4 * y;
[
s1_xx + s2_xx + s3_xx + s4_xx,
s1_yy + s2_yy + s3_yy + s4_yy,
s1_xy + s2_xy + s3_xy + s4_xy,
]
}
pub fn extract_ki_from_stress(&self, r: f64, sigma_yy_at_theta0: f64) -> f64 {
sigma_yy_at_theta0 * (2.0 * PI * r).sqrt()
}
}
pub struct LinearFracture;
impl LinearFracture {
pub fn stress_intensity_ki(sigma_far: f64, a: f64, geometry_factor: f64) -> f64 {
sigma_far * (PI * a).sqrt() * geometry_factor
}
pub fn stress_intensity_kii(tau: f64, a: f64, geometry_factor: f64) -> f64 {
tau * (PI * a).sqrt() * geometry_factor
}
pub fn crack_tip_stress(r: f64, theta: f64, ki: f64, kii: f64) -> [f64; 3] {
let half_theta = theta / 2.0;
let prefactor = 1.0 / (2.0 * PI * r).sqrt();
let cos_h = half_theta.cos();
let sin_h = half_theta.sin();
let cos_3h = (3.0 * half_theta).cos();
let sin_3h = (3.0 * half_theta).sin();
let sigma_xx = prefactor
* (ki * cos_h * (1.0 - sin_h * sin_3h) - kii * sin_h * (2.0 + cos_h * cos_3h));
let sigma_yy =
prefactor * (ki * cos_h * (1.0 + sin_h * sin_3h) + kii * sin_h * cos_h * cos_3h);
let tau_xy =
prefactor * (ki * sin_h * cos_h * cos_3h + kii * cos_h * (1.0 - sin_h * sin_3h));
[sigma_xx, sigma_yy, tau_xy]
}
pub fn griffith_critical_stress(a: f64, fracture_toughness: f64, geometry_factor: f64) -> f64 {
fracture_toughness / ((PI * a).sqrt() * geometry_factor)
}
pub fn j_integral_estimate(ki: f64, kii: f64, e_star: f64) -> f64 {
(ki * ki + kii * kii) / e_star
}
pub fn paris_law_da_dn(delta_k: f64, c: f64, m: f64) -> f64 {
c * delta_k.powf(m)
}
pub fn fatigue_life_cycles(
a_initial: f64,
a_final: f64,
delta_sigma: f64,
geometry_factor: f64,
c: f64,
m: f64,
) -> f64 {
let dk_coeff = (delta_sigma * geometry_factor * PI.sqrt()).powf(m);
let denominator_common = c * dk_coeff;
let exponent = 1.0 - m / 2.0;
if exponent.abs() < 1e-12 {
(a_final / a_initial).ln() / denominator_common
} else {
(a_final.powf(exponent) - a_initial.powf(exponent)) / (exponent * denominator_common)
}
}
}
pub struct SifMixed {
pub e: f64,
pub nu: f64,
pub plane_strain: bool,
}
impl SifMixed {
pub fn new(e: f64, nu: f64, plane_strain: bool) -> Self {
Self {
e,
nu,
plane_strain,
}
}
fn e_star(&self) -> f64 {
if self.plane_strain {
self.e / (1.0 - self.nu * self.nu)
} else {
self.e
}
}
pub fn compute_interaction_integral(&self, points: &[InteractionIntegralPoint]) -> f64 {
let mut integral = 0.0;
for pt in points {
let s1 = pt.stress_fem;
let s2 = pt.stress_aux;
let du1 = pt.grad_u_fem;
let du2 = pt.grad_u_aux;
let _e1 = pt.strain_fem;
let e2 = pt.strain_aux;
let w12 = s1[0] * e2[0] + s1[1] * e2[1] + 2.0 * s1[2] * e2[2];
let sw1 = s1[0] * du2[0] + s1[2] * du2[2] + s2[0] * du1[0] + s2[2] * du1[2];
let sw2 = s1[2] * du2[0] + s1[1] * du2[2] + s2[2] * du1[0] + s2[1] * du1[2];
let dq1 = pt.grad_q[0];
let dq2 = pt.grad_q[1];
let integrand = (sw1 - w12) * dq1 + sw2 * dq2;
integral += integrand * pt.dv;
}
integral
}
pub fn extract_ki(&self, interaction_integral: f64) -> f64 {
self.e_star() / 2.0 * interaction_integral
}
pub fn extract_kii(&self, interaction_integral: f64) -> f64 {
self.e_star() / 2.0 * interaction_integral
}
pub fn effective_sif(&self, ki: f64, kii: f64) -> f64 {
(ki * ki + kii * kii).sqrt()
}
pub fn is_fracture(&self, ki: f64, kii: f64, k_ic: f64) -> bool {
ki * ki + kii * kii >= k_ic * k_ic
}
}
#[derive(Debug, Clone)]
pub struct CzmBilinearParams {
pub t_n_max: f64,
pub t_s_max: f64,
pub delta_n_c: f64,
pub delta_s_c: f64,
pub g_c_i: f64,
pub g_c_ii: f64,
}
impl CzmBilinearParams {
pub fn from_peak_and_critical(
t_n_max: f64,
t_s_max: f64,
delta_n_c: f64,
delta_s_c: f64,
) -> Self {
Self {
t_n_max,
t_s_max,
delta_n_c,
delta_s_c,
g_c_i: 0.5 * t_n_max * delta_n_c,
g_c_ii: 0.5 * t_s_max * delta_s_c,
}
}
}
#[derive(Debug, Clone)]
pub struct DomainIntegralPoint {
pub stress: [f64; 3],
pub strain: [f64; 3],
pub grad_u: [f64; 4],
pub grad_q: [f64; 2],
pub dv: f64,
}
#[derive(Debug, Clone)]
pub struct ThreePointBendSpec {
pub span: f64,
pub width: f64,
pub crack_length: f64,
pub thickness: f64,
}
impl ThreePointBendSpec {
pub fn new(span: f64, width: f64, crack_length: f64, thickness: f64) -> Self {
Self {
span,
width,
crack_length,
thickness,
}
}
pub fn geometry_factor(&self) -> f64 {
let x = self.crack_length / self.width;
let num = 3.0 * x.sqrt() * (1.99 - x * (1.0 - x) * (2.15 - 3.93 * x + 2.7 * x * x));
let denom = 2.0 * (1.0 + 2.0 * x) * (1.0 - x).powf(1.5);
num / denom.max(f64::EPSILON)
}
pub fn k_from_load(&self, load: f64) -> f64 {
let f = self.geometry_factor();
(load * self.span) / (self.thickness * self.width * self.width) * f
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MixedModeCriterion {
MaxEnergyReleaseRate,
MinStrainEnergyDensity,
MaxTangentialStress,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CzmLaw {
Bilinear,
Exponential,
Trapezoidal,
Ppr,
}
#[derive(Debug, Clone)]
pub struct InteractionIntegralPoint {
pub stress_fem: [f64; 3],
pub grad_u_fem: [f64; 4],
pub stress_aux: [f64; 3],
pub grad_u_aux: [f64; 4],
pub strain_fem: [f64; 3],
pub strain_aux: [f64; 3],
pub grad_q: [f64; 2],
pub dv: f64,
}
pub struct JIntegral {
pub e: f64,
pub nu: f64,
pub plane_strain: bool,
}
impl JIntegral {
pub fn new(e: f64, nu: f64, plane_strain: bool) -> Self {
Self {
e,
nu,
plane_strain,
}
}
pub fn compute_domain_integral(&self, points: &[DomainIntegralPoint]) -> f64 {
let mut j = 0.0;
for pt in points {
let sxx = pt.stress[0];
let syy = pt.stress[1];
let sxy = pt.stress[2];
let exx = pt.strain[0];
let eyy = pt.strain[1];
let exy = pt.strain[2];
let w = 0.5 * (sxx * exx + syy * eyy + 2.0 * sxy * exy);
let du1_dx1 = pt.grad_u[0];
let du2_dx1 = pt.grad_u[2];
let dq_dx1 = pt.grad_q[0];
let dq_dx2 = pt.grad_q[1];
let sigma_du_dx1 = sxx * du1_dx1 + sxy * du2_dx1;
let sigma_du_dx2_part = sxy * du1_dx1 + syy * du2_dx1;
let integrand = (sigma_du_dx1 - w) * dq_dx1 + sigma_du_dx2_part * dq_dx2;
j += integrand * pt.dv;
}
j
}
pub fn j_from_sif(&self, ki: f64, kii: f64) -> f64 {
let e_eff = if self.plane_strain {
self.e / (1.0 - self.nu * self.nu)
} else {
self.e
};
(ki * ki + kii * kii) / e_eff
}
pub fn ki_from_j(&self, j: f64) -> f64 {
let e_eff = if self.plane_strain {
self.e / (1.0 - self.nu * self.nu)
} else {
self.e
};
(j * e_eff).sqrt()
}
}