use std::f64::consts::PI;
use super::types::{GtnParams, PhaseFieldParams};
#[allow(dead_code)]
pub fn crack_driving_force(strain_energy_density: f64, h_prev: f64) -> f64 {
strain_energy_density.max(h_prev)
}
#[allow(dead_code)]
pub fn degradation_function(d: f64) -> f64 {
pub(super) const K_RES: f64 = 1.0e-6;
(1.0 - d).powi(2) + K_RES
}
#[allow(dead_code)]
pub fn phase_field_residual(d: f64, h: f64, params: &PhaseFieldParams) -> f64 {
(params.gc / params.l0) * d - 2.0 * (1.0 - d) * h
}
#[allow(dead_code)]
pub fn update_phase_field(d_prev: f64, h: f64, params: &PhaseFieldParams) -> f64 {
let alpha = params.gc / params.l0;
let d_new = 2.0 * h / (alpha + 2.0 * h);
d_new.clamp(d_prev, 1.0)
}
#[allow(dead_code)]
pub fn effective_modulus(d: f64, e0: f64) -> f64 {
degradation_function(d) * e0
}
#[allow(dead_code)]
pub fn crack_length_from_field(d_field: &[f64], dx: f64, l0: f64) -> f64 {
let n = d_field.len();
if n == 0 {
return 0.0;
}
let mut gamma = 0.0;
for i in 0..n {
let grad = if n == 1 {
0.0
} else if i == 0 {
(d_field[1] - d_field[0]) / dx
} else if i == n - 1 {
(d_field[n - 1] - d_field[n - 2]) / dx
} else {
(d_field[i + 1] - d_field[i - 1]) / (2.0 * dx)
};
gamma += (d_field[i].powi(2) / (2.0 * l0) + (l0 / 2.0) * grad.powi(2)) * dx;
}
gamma
}
#[allow(dead_code)]
pub fn sif_center_crack_infinite(stress: f64, half_crack_length: f64) -> f64 {
stress * (PI * half_crack_length).sqrt()
}
#[allow(dead_code)]
pub fn sif_center_crack_finite_width(stress: f64, half_crack: f64, half_width: f64) -> f64 {
let ratio = half_crack / half_width;
let correction =
(1.0 - 0.025 * ratio.powi(2) + 0.06 * ratio.powi(4)) / (PI * ratio / 2.0).cos().sqrt();
stress * (PI * half_crack).sqrt() * correction
}
#[allow(dead_code)]
pub fn sif_single_edge_crack(stress: f64, crack_length: f64) -> f64 {
1.12 * stress * (PI * crack_length).sqrt()
}
#[allow(dead_code)]
pub fn sif_edge_crack_finite_width(stress: f64, crack_length: f64, width: f64) -> f64 {
let r = crack_length / width;
let f = 1.12 - 0.231 * r + 10.55 * r.powi(2) - 21.71 * r.powi(3) + 30.38 * r.powi(4);
stress * (PI * crack_length).sqrt() * f
}
#[allow(dead_code)]
pub fn sif_double_edge_crack(stress: f64, crack_length: f64, half_width: f64) -> f64 {
let r = crack_length / half_width;
let f = 1.12 + 0.203 * r - 1.197 * r.powi(2) + 1.93 * r.powi(3);
stress * (PI * crack_length).sqrt() * f
}
#[allow(dead_code)]
pub fn sif_penny_shaped_crack(stress: f64, crack_radius: f64) -> f64 {
(2.0 / PI) * stress * (PI * crack_radius).sqrt()
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn sif_compact_tension(load: f64, thickness: f64, width: f64, crack_length: f64) -> f64 {
let alpha = crack_length / width;
let f = (2.0 + alpha)
* (0.886 + 4.64 * alpha - 13.32 * alpha.powi(2) + 14.72 * alpha.powi(3)
- 5.6 * alpha.powi(4))
/ (1.0 - alpha).powf(1.5);
(load / (thickness * width.sqrt())) * f
}
#[allow(dead_code)]
pub fn weight_function_edge_crack(x: f64, a: f64, m1: f64, m2: f64, m3: f64) -> f64 {
if x >= a {
return 0.0;
}
let eta = 1.0 - x / a;
let denom = (2.0 * PI * (a - x)).sqrt();
(2.0 / denom) * (1.0 + m1 * eta.sqrt() + m2 * eta + m3 * eta.powf(1.5))
}
#[allow(dead_code)]
pub fn sif_weight_function(
stress_profile: &dyn Fn(f64) -> f64,
crack_length: f64,
m1: f64,
m2: f64,
m3: f64,
n_points: usize,
) -> f64 {
if crack_length <= 0.0 || n_points < 2 {
return 0.0;
}
let dx = crack_length / (n_points - 1) as f64;
let mut k = 0.0;
for i in 0..n_points {
let x = i as f64 * dx;
let x_safe = x.min(crack_length * (1.0 - 1e-10));
let sigma = stress_profile(x_safe);
let m = weight_function_edge_crack(x_safe, crack_length, m1, m2, m3);
let weight = if i == 0 || i == n_points - 1 {
0.5
} else {
1.0
};
k += weight * sigma * m * dx;
}
k
}
#[allow(dead_code)]
pub fn irwin_plastic_zone(k_i: f64, sigma_ys: f64, plane_strain: bool) -> f64 {
let factor = if plane_strain {
1.0 / (6.0 * PI)
} else {
1.0 / (2.0 * PI)
};
factor * (k_i / sigma_ys).powi(2)
}
#[allow(dead_code)]
pub fn irwin_effective_crack_length(
crack_length: f64,
k_i: f64,
sigma_ys: f64,
plane_strain: bool,
) -> f64 {
crack_length + irwin_plastic_zone(k_i, sigma_ys, plane_strain)
}
#[allow(dead_code)]
pub fn irwin_corrected_sif(
stress: f64,
crack_length: f64,
sigma_ys: f64,
plane_strain: bool,
max_iter: usize,
) -> f64 {
let mut a_eff = crack_length;
for _ in 0..max_iter {
let k = stress * (PI * a_eff).sqrt();
let rp = irwin_plastic_zone(k, sigma_ys, plane_strain);
let a_new = crack_length + rp;
if (a_new - a_eff).abs() < 1e-12 * crack_length {
break;
}
a_eff = a_new;
}
stress * (PI * a_eff).sqrt()
}
#[allow(dead_code)]
pub fn dugdale_plastic_zone(half_crack: f64, stress: f64, sigma_ys: f64) -> f64 {
let sec_val = 1.0 / (PI * stress / (2.0 * sigma_ys)).cos();
half_crack * (sec_val - 1.0)
}
#[allow(dead_code)]
pub fn dugdale_ctod(half_crack: f64, stress: f64, sigma_ys: f64, e_modulus: f64) -> f64 {
let sec_val = 1.0 / (PI * stress / (2.0 * sigma_ys)).cos();
(8.0 * sigma_ys * half_crack) / (PI * e_modulus) * sec_val.ln()
}
#[allow(dead_code)]
pub fn ctod_from_k(k_i: f64, e_modulus: f64, sigma_ys: f64, nu: f64, plane_strain: bool) -> f64 {
let factor = if plane_strain { 1.0 - nu * nu } else { 1.0 };
factor * k_i.powi(2) / (e_modulus * sigma_ys)
}
#[allow(dead_code)]
pub fn ctod_criterion(ctod: f64, ctod_critical: f64) -> bool {
ctod >= ctod_critical
}
#[allow(dead_code)]
pub fn ctoa_from_ctod(ctod: f64, distance_behind_tip: f64) -> f64 {
2.0 * (ctod / (2.0 * distance_behind_tip)).atan()
}
#[allow(dead_code)]
pub fn ctoa_criterion(ctoa: f64, ctoa_critical: f64) -> bool {
ctoa >= ctoa_critical
}
#[allow(dead_code)]
pub fn gtn_yield_function(
sigma_eq: f64,
sigma_m: f64,
sigma_y: f64,
f_star: f64,
params: &GtnParams,
) -> f64 {
let term1 = (sigma_eq / sigma_y).powi(2);
let term2 = 2.0 * params.q1 * f_star * (3.0 * params.q2 * sigma_m / (2.0 * sigma_y)).cosh();
let term3 = 1.0 + params.q3 * f_star.powi(2);
term1 + term2 - term3
}
#[allow(dead_code)]
pub fn nucleation_rate(eps_p: f64, deps_p: f64, fn_void: f64, en: f64, sn: f64) -> f64 {
let exponent = -0.5 * ((eps_p - en) / sn).powi(2);
(fn_void / (sn * (2.0 * PI).sqrt())) * exponent.exp() * deps_p
}
#[allow(dead_code)]
pub fn void_growth_rate(f: f64, deps_vol_p: f64) -> f64 {
(1.0 - f) * deps_vol_p
}
#[allow(dead_code)]
pub fn effective_void_fraction(f: f64, params: &GtnParams) -> f64 {
if f <= params.fc {
f
} else {
let delta_f_star = (1.0 / params.q1 - params.fc) / (params.ff - params.fc);
params.fc + delta_f_star * (f - params.fc)
}
}
#[allow(dead_code)]
pub fn gtn_step(f: f64, eps_p: f64, deps_p: f64, deps_vol_p: f64, params: &GtnParams) -> f64 {
let df_growth = void_growth_rate(f, deps_vol_p);
let df_nucleation = nucleation_rate(eps_p, deps_p, params.fn_void, params.en, params.sn);
(f + df_growth + df_nucleation).clamp(0.0, 1.0)
}
#[allow(dead_code)]
pub fn k_to_g(k_i: f64, e_modulus: f64, nu: f64, plane_strain: bool) -> f64 {
let factor = if plane_strain { 1.0 - nu * nu } else { 1.0 };
factor * k_i.powi(2) / e_modulus
}
#[allow(dead_code)]
pub fn g_to_k(g: f64, e_modulus: f64, nu: f64, plane_strain: bool) -> f64 {
let factor = if plane_strain { 1.0 - nu * nu } else { 1.0 };
(g * e_modulus / factor).sqrt()
}
#[allow(dead_code)]
pub fn paris_law_growth_rate(delta_k: f64, c: f64, m: f64) -> f64 {
if delta_k <= 0.0 {
return 0.0;
}
c * delta_k.powf(m)
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn paris_forman_growth_rate(
delta_k: f64,
k_max: f64,
c: f64,
m: f64,
delta_k_th: f64,
k_ic: f64,
) -> f64 {
if delta_k <= delta_k_th || k_max >= k_ic {
return if k_max >= k_ic { f64::INFINITY } else { 0.0 };
}
let threshold_factor = 1.0 - delta_k_th / delta_k;
let instability_factor = 1.0 - k_max / k_ic;
if instability_factor <= 0.0 {
return f64::INFINITY;
}
c * delta_k.powf(m) * threshold_factor / instability_factor
}
#[allow(dead_code)]
pub fn sif_three_point_bend(
force: f64,
span: f64,
thickness: f64,
width: f64,
crack_length: f64,
) -> f64 {
let alpha = crack_length / width;
let alpha_sqrt = alpha.sqrt();
let poly = 1.99 - alpha * (1.0 - alpha) * (2.15 - 3.93 * alpha + 2.7 * alpha * alpha);
let f_alpha = (3.0 * alpha_sqrt * poly) / (2.0 * (1.0 + 2.0 * alpha) * (1.0 - alpha).powf(1.5));
(force * span) / (thickness * width.sqrt()) * f_alpha
}
#[allow(dead_code)]
pub fn senb_critical_crack_length(
force: f64,
span: f64,
thickness: f64,
width: f64,
k_ic: f64,
n_iter: usize,
) -> Option<f64> {
let a_min = 0.01 * width;
let a_max = 0.9 * width;
let f_lo = sif_three_point_bend(force, span, thickness, width, a_min) - k_ic;
let f_hi = sif_three_point_bend(force, span, thickness, width, a_max) - k_ic;
if f_lo * f_hi > 0.0 {
return None;
}
let mut lo = a_min;
let mut hi = a_max;
for _ in 0..n_iter {
let mid = 0.5 * (lo + hi);
let f_mid = sif_three_point_bend(force, span, thickness, width, mid) - k_ic;
if f_mid * f_lo < 0.0 {
hi = mid;
} else {
lo = mid;
}
if (hi - lo) / width < 1e-12 {
break;
}
}
Some(0.5 * (lo + hi))
}
#[allow(dead_code)]
pub fn sif_mixed_mode_inclined_crack(stress: f64, half_crack: f64, angle_rad: f64) -> (f64, f64) {
let k_base = stress * (PI * half_crack).sqrt();
let k_i = k_base * angle_rad.cos().powi(2);
let k_ii = k_base * angle_rad.sin() * angle_rad.cos();
(k_i, k_ii)
}
#[allow(dead_code)]
pub fn sif_mode_iii_edge_crack(shear_stress: f64, crack_length: f64) -> f64 {
shear_stress * (PI * crack_length).sqrt()
}
#[allow(dead_code)]
pub fn sif_effective_mixed_mode(k_i: f64, k_ii: f64) -> f64 {
(k_i.powi(2) + k_ii.powi(2)).sqrt()
}
#[allow(dead_code)]
pub fn sif_effective_mixed_mode_iii(k_i: f64, k_ii: f64, k_iii: f64, nu: f64) -> f64 {
(k_i.powi(2) + k_ii.powi(2) + k_iii.powi(2) / (1.0 - nu)).sqrt()
}
#[allow(dead_code)]
pub fn mixed_mode_propagation_angle(k_i: f64, k_ii: f64) -> f64 {
if k_ii.abs() < 1e-30 {
return 0.0;
}
let ratio = k_i / (4.0 * k_ii);
let inner = (ratio * ratio + 0.5).sqrt();
2.0 * (ratio - inner).atan()
}
#[allow(dead_code)]
pub fn walker_delta_k_eff(delta_k: f64, r_ratio: f64, gamma: f64) -> f64 {
let denom = (1.0 - r_ratio).powf(1.0 - gamma);
if denom <= 0.0 {
return f64::INFINITY;
}
delta_k / denom
}
#[allow(dead_code)]
pub fn paris_walker_rate(delta_k: f64, r_ratio: f64, c: f64, m: f64, gamma: f64) -> f64 {
if delta_k <= 0.0 {
return 0.0;
}
let dk_eff = walker_delta_k_eff(delta_k, r_ratio, gamma);
c * dk_eff.powf(m)
}
#[allow(dead_code)]
pub fn forman_crack_growth_rate(delta_k: f64, r_ratio: f64, c: f64, m: f64, k_c: f64) -> f64 {
if delta_k <= 0.0 {
return 0.0;
}
let denom = (1.0 - r_ratio) * k_c - delta_k;
if denom <= 0.0 {
return f64::INFINITY;
}
c * delta_k.powf(m) / denom
}
#[allow(dead_code)]
pub fn plastic_zone_size_first_order(k_i: f64, sigma_ys: f64, plane_strain: bool) -> f64 {
let factor = if plane_strain {
1.0 / (6.0 * PI)
} else {
1.0 / (2.0 * PI)
};
factor * (k_i / sigma_ys).powi(2)
}
#[allow(dead_code)]
pub fn plastic_zone_size_iterative(
stress: f64,
crack_length: f64,
sigma_ys: f64,
plane_strain: bool,
max_iter: usize,
) -> f64 {
let factor = if plane_strain {
1.0 / (6.0 * PI)
} else {
1.0 / (2.0 * PI)
};
let mut rp = 0.0_f64;
for _ in 0..max_iter {
let a_eff = crack_length + rp;
let k = stress * (PI * a_eff).sqrt();
let rp_new = factor * (k / sigma_ys).powi(2);
if (rp_new - rp).abs() < 1e-15 * (1.0 + rp) {
return rp_new;
}
rp = rp_new;
}
rp
}
#[allow(dead_code)]
#[allow(non_snake_case)]
pub fn plastic_zone_biaxial(k_i: f64, sigma_ys: f64, lambda: f64, plane_strain: bool) -> f64 {
let vm_factor = (1.0 - lambda + lambda * lambda).sqrt();
let sigma_ys_eff = sigma_ys / vm_factor;
let factor = if plane_strain {
1.0 / (6.0 * PI)
} else {
1.0 / (2.0 * PI)
};
factor * (k_i / sigma_ys_eff).powi(2)
}
#[allow(dead_code)]
pub fn ctod_irwin(k_i: f64, sigma_ys: f64, e: f64, nu: f64, plane_strain: bool) -> f64 {
let e_prime = if plane_strain { e / (1.0 - nu * nu) } else { e };
k_i.powi(2) / (sigma_ys * e_prime)
}
#[allow(dead_code)]
pub fn ctod_wells(k_i: f64, e: f64, sigma_ys: f64) -> f64 {
(4.0 / PI) * k_i.powi(2) / (e * sigma_ys)
}
#[allow(dead_code)]
pub fn ctod_from_j(j_integral: f64, sigma_ys: f64, plane_strain: bool) -> f64 {
let m = if plane_strain { 2.0 } else { 1.0 };
j_integral / (m * sigma_ys)
}
#[allow(dead_code)]
pub fn j_from_ctod(ctod: f64, sigma_ys: f64, plane_strain: bool) -> f64 {
let m = if plane_strain { 2.0 } else { 1.0 };
m * sigma_ys * ctod
}
#[allow(dead_code)]
pub fn minimum_thickness_plane_strain(k_ic: f64, sigma_ys: f64) -> f64 {
2.5 * (k_ic / sigma_ys).powi(2)
}
#[allow(dead_code)]
pub fn is_plane_strain_valid(
thickness: f64,
crack_length: f64,
width: f64,
k_ic: f64,
sigma_ys: f64,
) -> bool {
let b_min = minimum_thickness_plane_strain(k_ic, sigma_ys);
thickness >= b_min && crack_length >= b_min && (width - crack_length) >= b_min
}
#[allow(dead_code)]
pub fn k_to_j(k_ic: f64, e: f64, nu: f64) -> f64 {
k_ic.powi(2) * (1.0 - nu * nu) / e
}
#[allow(dead_code)]
pub fn j_to_k(j_c: f64, e: f64, nu: f64) -> f64 {
(j_c * e / (1.0 - nu * nu)).sqrt()
}
#[allow(dead_code)]
pub fn sif_dynamic_freund(k_static: f64, crack_velocity: f64, rayleigh_speed: f64) -> f64 {
if rayleigh_speed <= 0.0 || crack_velocity >= rayleigh_speed {
return 0.0;
}
let ratio = crack_velocity / rayleigh_speed;
k_static * (1.0 - ratio).sqrt()
}
#[allow(dead_code)]
pub fn rayleigh_wave_speed(shear_modulus: f64, density: f64, nu: f64) -> f64 {
let c_s = (shear_modulus / density).sqrt();
c_s * (0.862 + 1.14 * nu) / (1.0 + nu)
}
#[allow(dead_code)]
pub fn will_crack_arrest(k_static: f64, k_ia: f64) -> bool {
k_static < k_ia
}
#[allow(dead_code)]
pub fn mode_mixity_angle(k_i: f64, k_ii: f64) -> f64 {
k_ii.atan2(k_i)
}
#[allow(dead_code)]
pub fn mode_mixity_angle_degrees(k_i: f64, k_ii: f64) -> f64 {
mode_mixity_angle(k_i, k_ii).to_degrees()
}
#[allow(dead_code)]
pub fn mode_i_fraction(k_i: f64, k_ii: f64, k_iii: f64) -> f64 {
let total = k_i * k_i + k_ii * k_ii + k_iii * k_iii;
if total <= 0.0 {
return 1.0;
}
k_i * k_i / total
}
#[allow(dead_code)]
pub fn stress_triaxiality(sigma_m: f64, sigma_eq: f64) -> f64 {
if sigma_eq.abs() < 1e-30 {
return 0.0;
}
sigma_m / sigma_eq
}
#[allow(dead_code)]
pub fn rice_tracey_void_growth(sigma_m: f64, sigma_0: f64, eps_dot_eq: f64) -> f64 {
let c = 0.283;
c * (1.5 * sigma_m / sigma_0).exp() * eps_dot_eq
}
#[allow(dead_code)]
pub fn t_stress_constraint(t_stress: f64, sigma_ys: f64) -> f64 {
if sigma_ys.abs() < 1e-30 {
return 0.0;
}
t_stress / sigma_ys
}
#[allow(dead_code)]
pub fn coffin_manson_strain_amplitude(n_f: f64, eps_f_prime: f64, c: f64) -> f64 {
eps_f_prime * (2.0 * n_f).powf(c)
}
#[allow(dead_code)]
pub fn basquin_stress_amplitude(n_f: f64, sigma_f_prime: f64, b: f64) -> f64 {
sigma_f_prime * (2.0 * n_f).powf(b)
}
#[allow(dead_code)]
pub fn morrow_total_strain_amplitude(
n_f: f64,
sigma_f_prime: f64,
b: f64,
eps_f_prime: f64,
c: f64,
e_modulus: f64,
) -> f64 {
sigma_f_prime / e_modulus * (2.0 * n_f).powf(b) + eps_f_prime * (2.0 * n_f).powf(c)
}
#[allow(dead_code)]
pub fn basquin_fatigue_life(delta_sigma_over_2: f64, sigma_f_prime: f64, b: f64) -> f64 {
if sigma_f_prime <= 0.0 || b >= 0.0 {
return f64::INFINITY;
}
0.5 * (delta_sigma_over_2 / sigma_f_prime).powf(1.0 / b)
}
#[allow(dead_code)]
pub fn swt_parameter(sigma_max: f64, strain_amplitude: f64) -> f64 {
sigma_max * strain_amplitude
}
#[allow(dead_code)]
pub fn sif_hole_edge_crack(stress: f64, crack_length: f64, hole_radius: f64) -> f64 {
let lambda = crack_length / hole_radius;
let f = 1.0 + 0.1215 * lambda - 0.0598 * lambda * lambda;
stress * (PI * crack_length).sqrt() * f.max(1.0)
}
#[allow(dead_code)]
pub fn sif_surface_crack(stress: f64, depth: f64, half_length: f64) -> f64 {
let ratio = depth / half_length;
let phi2 = if ratio <= 1.0 {
1.0 + 4.593 * ratio.powf(1.65)
} else {
1.0 + 4.593 * (1.0 / ratio).powf(1.65)
};
stress * (PI * depth / phi2).sqrt()
}
#[allow(dead_code)]
pub fn near_tip_stress_mode_i(k_i: f64, r: f64, theta: f64) -> (f64, f64, f64) {
if r <= 0.0 {
return (f64::INFINITY, f64::INFINITY, f64::INFINITY);
}
let coeff = k_i / (2.0 * PI * r).sqrt();
let half = theta / 2.0;
let sigma_rr = coeff * half.cos() * (1.0 - half.sin() * (3.0 * half).sin());
let sigma_tt = coeff * half.cos() * (1.0 + half.sin() * (3.0 * half).sin());
let sigma_rt = coeff * half.sin() * half.cos() * (3.0 * half).cos();
(sigma_rr, sigma_tt, sigma_rt)
}
#[allow(dead_code)]
pub fn paris_law_cycles(
a0: f64,
a_f: f64,
stress_range: f64,
c: f64,
m: f64,
k_ic: f64,
n_steps: usize,
) -> f64 {
let mut a = a0;
let mut n_total = 0.0;
let da = (a_f - a0) / n_steps as f64;
for _ in 0..n_steps {
let k_max = stress_range * (PI * a).sqrt();
if k_max >= k_ic {
break;
}
let delta_k = stress_range * (PI * a).sqrt();
let da_dn = paris_law_growth_rate(delta_k, c, m);
if da_dn <= 0.0 {
break;
}
n_total += da / da_dn;
a += da;
if a >= a_f {
break;
}
}
n_total
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn forman_law_cycles(
a0: f64,
a_f: f64,
delta_sigma: f64,
r_ratio: f64,
c: f64,
m: f64,
k_c: f64,
n_steps: usize,
) -> f64 {
let mut a = a0;
let mut n_total = 0.0;
let da = (a_f - a0) / n_steps as f64;
for _ in 0..n_steps {
let delta_k = delta_sigma * (PI * a).sqrt();
let k_max = delta_k / (1.0 - r_ratio).max(1e-10);
if k_max >= k_c {
break;
}
let da_dn = forman_crack_growth_rate(delta_k, r_ratio, c, m, k_c);
if da_dn <= 0.0 || !da_dn.is_finite() {
break;
}
n_total += da / da_dn;
a += da;
if a >= a_f {
break;
}
}
n_total
}