#[allow(unused_imports)]
use super::functions::*;
#[derive(Debug, Clone)]
pub struct BaileyNortonCreep {
pub b: f64,
pub m: f64,
pub p: f64,
}
impl BaileyNortonCreep {
pub fn new(b: f64, m: f64, p: f64) -> Self {
Self { b, m, p }
}
pub fn creep_strain(&self, sigma: f64, time: f64) -> f64 {
self.b * sigma.powf(self.m) * time.powf(self.p)
}
pub fn creep_rate(&self, sigma: f64, time: f64) -> f64 {
self.b * sigma.powf(self.m) * self.p * time.powf(self.p - 1.0)
}
}
#[derive(Debug, Clone)]
pub struct CdmDamage {
pub d: f64,
pub eps0: f64,
pub eps_f: f64,
pub param: f64,
pub law: DamageEvolutionLaw,
pub kappa: f64,
}
impl CdmDamage {
pub fn new(eps0: f64, eps_f: f64, param: f64, law: DamageEvolutionLaw) -> Self {
Self {
d: 0.0,
eps0,
eps_f,
param,
law,
kappa: 0.0,
}
}
pub fn update(&mut self, equivalent_strain: f64) {
if equivalent_strain <= self.kappa {
return;
}
self.kappa = equivalent_strain;
if equivalent_strain <= self.eps0 {
return;
}
let d_new = match self.law {
DamageEvolutionLaw::Linear => {
let range = self.eps_f - self.eps0;
if range.abs() < f64::EPSILON {
1.0
} else {
let eps = equivalent_strain;
(self.eps_f / eps) * (eps - self.eps0) / range
}
}
DamageEvolutionLaw::Exponential => {
let delta = equivalent_strain - self.eps0;
1.0 - (-self.param * delta).exp()
}
DamageEvolutionLaw::PowerLaw => {
let range = self.eps_f - self.eps0;
if range.abs() < f64::EPSILON {
1.0
} else {
let ratio = ((equivalent_strain - self.eps0) / range).min(1.0);
ratio.powf(self.param)
}
}
};
self.d = d_new.clamp(self.d, 1.0);
}
#[allow(dead_code)]
pub fn effective_stiffness(&self, young_modulus: f64) -> f64 {
(1.0 - self.d) * young_modulus
}
#[allow(dead_code)]
pub fn effective_stress(&self, nominal_stress: f64) -> f64 {
let w = 1.0 - self.d;
if w < 1e-15 {
f64::INFINITY
} else {
nominal_stress / w
}
}
pub fn compute_localization_indicator(&self, young_modulus: f64, poisson_ratio: f64) -> f64 {
let nu = poisson_ratio;
let w = 1.0 - self.d;
let reg = 1e-15_f64;
let damage_term = self.d / (w * w + reg);
let geometric_factor = if (1.0 - nu).abs() > 1e-15 {
(1.0 - 2.0 * nu) / (2.0 * (1.0 - nu))
} else {
1.0
};
let _ = young_modulus;
damage_term * geometric_factor.max(1e-15)
}
}
#[derive(Debug, Clone)]
pub struct NonLocalRegularization {
pub lc: f64,
}
impl NonLocalRegularization {
pub fn new(lc: f64) -> Self {
Self { lc }
}
#[allow(dead_code)]
pub fn weight(&self, distance: f64) -> f64 {
(-distance * distance / (2.0 * self.lc * self.lc)).exp()
}
#[allow(dead_code)]
pub fn weight_bell(&self, distance: f64) -> f64 {
if distance >= self.lc {
0.0
} else {
let ratio = distance / self.lc;
let t = 1.0 - ratio * ratio;
t * t
}
}
#[allow(dead_code)]
pub fn nonlocal_average(
&self,
point: &[f64; 3],
positions: &[[f64; 3]],
values: &[f64],
) -> f64 {
let mut weighted_sum = 0.0;
let mut weight_sum = 0.0;
for (pos, &val) in positions.iter().zip(values.iter()) {
let dx = point[0] - pos[0];
let dy = point[1] - pos[1];
let dz = point[2] - pos[2];
let dist = (dx * dx + dy * dy + dz * dz).sqrt();
let w = self.weight(dist);
weighted_sum += w * val;
weight_sum += w;
}
if weight_sum.abs() < f64::EPSILON {
0.0
} else {
weighted_sum / weight_sum
}
}
#[allow(dead_code)]
pub fn nonlocal_average_all(&self, positions: &[[f64; 3]], values: &[f64]) -> Vec<f64> {
positions
.iter()
.map(|pt| self.nonlocal_average(pt, positions, values))
.collect()
}
}
#[derive(Debug, Clone)]
pub struct IsotropicDamage {
pub d: f64,
pub energy_release_threshold: f64,
pub damage_evolution_rate: f64,
}
impl IsotropicDamage {
pub fn new(y0: f64, evolution_rate: f64) -> Self {
Self {
d: 0.0,
energy_release_threshold: y0,
damage_evolution_rate: evolution_rate,
}
}
pub fn strain_energy_release_rate(&self, sigma: f64, young_modulus: f64) -> f64 {
let integrity = 1.0 - self.d;
sigma * sigma / (2.0 * young_modulus * integrity * integrity)
}
pub fn update(&mut self, sigma: f64, young_modulus: f64) {
let y = self.strain_energy_release_rate(sigma, young_modulus);
let y0 = self.energy_release_threshold;
if y > y0 {
let a = self.damage_evolution_rate;
let d_new = 1.0 - (y0 / y) * (-(a * (y - y0) / y0)).exp();
let d_new = d_new.clamp(self.d, 1.0);
self.d = d_new;
}
}
pub fn effective_stiffness(&self, young_modulus: f64) -> f64 {
young_modulus * (1.0 - self.d)
}
pub fn effective_stress(&self, nominal_stress: f64) -> f64 {
let integrity = 1.0 - self.d;
if integrity < 1.0e-15 {
f64::INFINITY
} else {
nominal_stress / integrity
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ProgressiveFailureAnalysis {
pub n_plies: usize,
pub ply_damage: Vec<f64>,
pub criteria: HashinFailureCriteria,
}
impl ProgressiveFailureAnalysis {
#[allow(clippy::too_many_arguments)]
pub fn new(n_plies: usize, x_t: f64, x_c: f64, y_t: f64, y_c: f64, s12: f64) -> Self {
Self {
n_plies,
ply_damage: vec![0.0; n_plies],
criteria: HashinFailureCriteria::new(x_t, x_c, y_t, y_c, s12),
}
}
pub fn degrade_ply(&mut self, ply_idx: usize, d: f64) {
if ply_idx < self.n_plies {
self.ply_damage[ply_idx] = self.ply_damage[ply_idx].max(d).clamp(0.0, 1.0);
}
}
pub fn is_ply_failed(&self, ply_idx: usize) -> bool {
self.ply_damage.get(ply_idx).copied().unwrap_or(0.0) >= 1.0 - f64::EPSILON
}
pub fn is_fully_failed(&self) -> bool {
self.ply_damage.iter().all(|&d| d >= 1.0 - f64::EPSILON)
}
pub fn mean_damage(&self) -> f64 {
if self.n_plies == 0 {
return 0.0;
}
self.ply_damage.iter().sum::<f64>() / self.n_plies as f64
}
pub fn apply_stress(
&mut self,
ply_idx: usize,
sigma1: f64,
sigma2: f64,
tau12: f64,
d_increment: f64,
) {
if ply_idx < self.n_plies && self.criteria.is_failed(sigma1, sigma2, tau12) {
self.degrade_ply(ply_idx, self.ply_damage[ply_idx] + d_increment);
}
}
pub fn laminate_stiffness_factor(&self) -> f64 {
if self.n_plies == 0 {
return 0.0;
}
self.ply_damage.iter().map(|&d| 1.0 - d).sum::<f64>() / self.n_plies as f64
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DamageInducedAnisotropy {
pub e0: f64,
pub nu0: f64,
pub d11: f64,
pub d22: f64,
pub d33: f64,
pub d12: f64,
pub d13: f64,
pub d23: f64,
}
impl DamageInducedAnisotropy {
pub fn new(e0: f64, nu0: f64) -> Self {
Self {
e0,
nu0,
d11: 0.0,
d22: 0.0,
d33: 0.0,
d12: 0.0,
d13: 0.0,
d23: 0.0,
}
}
pub fn update_shear_damage(&mut self, d12: f64, d13: f64, d23: f64) {
self.d12 = self.d12.max(d12).clamp(0.0, 1.0 - f64::EPSILON);
self.d13 = self.d13.max(d13).clamp(0.0, 1.0 - f64::EPSILON);
self.d23 = self.d23.max(d23).clamp(0.0, 1.0 - f64::EPSILON);
}
pub fn update_normal_damage(&mut self, d11: f64, d22: f64, d33: f64) {
self.d11 = self.d11.max(d11).clamp(0.0, 1.0 - f64::EPSILON);
self.d22 = self.d22.max(d22).clamp(0.0, 1.0 - f64::EPSILON);
self.d33 = self.d33.max(d33).clamp(0.0, 1.0 - f64::EPSILON);
}
pub fn e1_eff(&self) -> f64 {
(1.0 - self.d11) * self.e0
}
pub fn e2_eff(&self) -> f64 {
(1.0 - self.d22) * self.e0
}
pub fn effective_shear_modulus_12(&self) -> f64 {
let g0 = self.e0 / (2.0 * (1.0 + self.nu0));
(1.0 - self.d12) * g0
}
pub fn damage_intensity(&self) -> f64 {
(self.d11.powi(2)
+ self.d22.powi(2)
+ self.d33.powi(2)
+ 2.0 * self.d12.powi(2)
+ 2.0 * self.d13.powi(2)
+ 2.0 * self.d23.powi(2))
.sqrt()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DamageEvolutionLaw {
Linear,
Exponential,
PowerLaw,
}
#[derive(Debug, Clone)]
pub struct MazarsDamage {
pub d: f64,
pub k: f64,
pub at: f64,
pub bt: f64,
pub ac: f64,
pub bc: f64,
}
impl MazarsDamage {
pub fn new(k0: f64, at: f64, bt: f64, ac: f64, bc: f64) -> Self {
Self {
d: 0.0,
k: k0,
at,
bt,
ac,
bc,
}
}
pub fn equivalent_strain(eps_principal: &[f64; 3]) -> f64 {
let sum_sq: f64 = eps_principal
.iter()
.map(|&e| {
let ep = e.max(0.0);
ep * ep
})
.sum();
sum_sq.sqrt()
}
pub fn update(&mut self, eps_principal: &[f64; 3]) {
let eps_tilde = Self::equivalent_strain(eps_principal);
if eps_tilde > self.k {
let k = self.k;
let factor = 1.0 - k / eps_tilde;
let delta = eps_tilde - k;
let d_new = self.at * factor * (-self.bt * delta).exp()
+ self.ac * factor * (-self.bc * delta).exp();
let d_new = d_new.clamp(self.d, 1.0);
self.d = d_new;
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TsaiWuCriterion {
pub x_t: f64,
pub x_c: f64,
pub y_t: f64,
pub y_c: f64,
pub s12: f64,
pub f12_star: f64,
}
impl TsaiWuCriterion {
#[allow(clippy::too_many_arguments)]
pub fn new(x_t: f64, x_c: f64, y_t: f64, y_c: f64, s12: f64, f12_star: f64) -> Self {
Self {
x_t,
x_c,
y_t,
y_c,
s12,
f12_star,
}
}
pub fn failure_index(&self, sigma1: f64, sigma2: f64, tau12: f64) -> f64 {
let f1 = 1.0 / self.x_t - 1.0 / self.x_c;
let f2 = 1.0 / self.y_t - 1.0 / self.y_c;
let f11 = 1.0 / (self.x_t * self.x_c);
let f22 = 1.0 / (self.y_t * self.y_c);
let f66 = 1.0 / (self.s12 * self.s12);
let f12 = self.f12_star / (self.x_t * self.x_c * self.y_t * self.y_c).sqrt();
f1 * sigma1
+ f2 * sigma2
+ f11 * sigma1 * sigma1
+ f22 * sigma2 * sigma2
+ f66 * tau12 * tau12
+ 2.0 * f12 * sigma1 * sigma2
}
pub fn strength_ratio(&self, sigma1: f64, sigma2: f64, tau12: f64) -> f64 {
let f1 = 1.0 / self.x_t - 1.0 / self.x_c;
let f2 = 1.0 / self.y_t - 1.0 / self.y_c;
let f11 = 1.0 / (self.x_t * self.x_c);
let f22 = 1.0 / (self.y_t * self.y_c);
let f66 = 1.0 / (self.s12 * self.s12);
let f12 = self.f12_star / (self.x_t * self.x_c * self.y_t * self.y_c).sqrt();
let a_coef = f11 * sigma1 * sigma1
+ f22 * sigma2 * sigma2
+ f66 * tau12 * tau12
+ 2.0 * f12 * sigma1 * sigma2;
let b_coef = f1 * sigma1 + f2 * sigma2;
if a_coef.abs() < f64::EPSILON {
if b_coef.abs() < f64::EPSILON {
return f64::INFINITY;
}
return 1.0 / b_coef;
}
let discriminant = b_coef * b_coef + 4.0 * a_coef;
if discriminant < 0.0 {
return f64::INFINITY;
}
(-b_coef + discriminant.sqrt()) / (2.0 * a_coef)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RepairEffectiveness {
pub repair_factor: f64,
pub residual_damage: f64,
}
impl RepairEffectiveness {
pub fn new(repair_factor: f64, residual_damage: f64) -> Self {
Self {
repair_factor: repair_factor.clamp(0.0, 1.0),
residual_damage: residual_damage.clamp(0.0, 1.0 - f64::EPSILON),
}
}
pub fn effectiveness(&self) -> f64 {
self.repair_factor * (1.0 - self.residual_damage)
}
pub fn strength_recovery(&self, original_strength: f64) -> f64 {
original_strength * self.effectiveness()
}
pub fn stiffness_recovery_factor(&self) -> f64 {
1.0 - self.residual_damage * (1.0 - self.repair_factor)
}
pub fn is_acceptable(&self, min_effectiveness: f64) -> bool {
self.effectiveness() >= min_effectiveness
}
}
#[derive(Debug, Clone)]
pub struct LemaitreDuctileDamage {
pub d: f64,
pub eps_p: f64,
pub eps_d: f64,
pub s_param: f64,
pub s_exp: f64,
pub d_critical: f64,
pub nu: f64,
}
impl LemaitreDuctileDamage {
#[allow(clippy::too_many_arguments)]
pub fn new(eps_d: f64, s_param: f64, s_exp: f64, d_critical: f64, nu: f64) -> Self {
Self {
d: 0.0,
eps_p: 0.0,
eps_d,
s_param,
s_exp,
d_critical,
nu,
}
}
#[allow(dead_code)]
pub fn triaxiality_function(&self, triaxiality: f64) -> f64 {
(2.0 / 3.0) * (1.0 + self.nu) + 3.0 * (1.0 - 2.0 * self.nu) * triaxiality * triaxiality
}
#[allow(dead_code)]
pub fn energy_release_rate(&self, sigma_eq: f64, triaxiality: f64, young_modulus: f64) -> f64 {
let rv = self.triaxiality_function(triaxiality);
let w = 1.0 - self.d;
sigma_eq * sigma_eq * rv / (2.0 * young_modulus * w * w)
}
#[allow(dead_code)]
pub fn update(
&mut self,
sigma_eq: f64,
triaxiality: f64,
young_modulus: f64,
delta_eps_p: f64,
) {
self.eps_p += delta_eps_p;
if self.eps_p <= self.eps_d || self.d >= self.d_critical {
return;
}
let y = self.energy_release_rate(sigma_eq, triaxiality, young_modulus);
let dd = (y / self.s_param).powf(self.s_exp) * delta_eps_p;
self.d = (self.d + dd).min(self.d_critical);
}
#[allow(dead_code)]
pub fn is_ruptured(&self) -> bool {
self.d >= self.d_critical - 1e-15
}
pub fn compute_triaxiality_correction(&self, triaxiality: f64) -> f64 {
(2.0 / 3.0) * (1.0 + self.nu) + 3.0 * (1.0 - 2.0 * self.nu) * triaxiality * triaxiality
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ProgressiveDamage {
pub e0: f64,
pub nu: f64,
pub d11: f64,
pub d22: f64,
}
impl ProgressiveDamage {
pub fn new(e0: f64, nu: f64, d11: f64, d22: f64) -> Self {
Self {
e0,
nu,
d11: d11.clamp(0.0, 1.0 - f64::EPSILON),
d22: d22.clamp(0.0, 1.0 - f64::EPSILON),
}
}
pub fn e11_eff(&self) -> f64 {
(1.0 - self.d11) * self.e0
}
pub fn e22_eff(&self) -> f64 {
(1.0 - self.d22) * self.e0
}
pub fn effective_stiffness_tensor(&self) -> [[f64; 3]; 3] {
let e1 = self.e11_eff();
let e2 = self.e22_eff();
let nu12 = self.nu;
let nu21 = nu12 * e2 / e1;
let denom = 1.0 - nu12 * nu21;
let c11 = e1 / denom;
let c22 = e2 / denom;
let c12 = nu12 * e2 / denom;
let g0 = self.e0 / (2.0 * (1.0 + self.nu));
let d_shear = 1.0 - (1.0 - self.d11) * (1.0 - self.d22);
let c66 = (1.0 - d_shear) * g0;
[[c11, c12, 0.0], [c12, c22, 0.0], [0.0, 0.0, c66]]
}
pub fn update_damage(&mut self, d11_new: f64, d22_new: f64) {
self.d11 = self.d11.max(d11_new).clamp(0.0, 1.0 - f64::EPSILON);
self.d22 = self.d22.max(d22_new).clamp(0.0, 1.0 - f64::EPSILON);
}
pub fn residual_strength(&self, sigma0: f64, _alpha_m: f64) -> f64 {
sigma0 * (1.0 - self.d11)
}
}
#[derive(Debug, Clone)]
pub struct GradientEnhancedDamage {
pub c: f64,
}
impl GradientEnhancedDamage {
pub fn new(lc: f64) -> Self {
Self { c: lc * lc }
}
#[allow(dead_code)]
pub fn solve_1d(&self, local_strains: &[f64], element_size: f64) -> Vec<f64> {
let n = local_strains.len();
if n == 0 {
return vec![];
}
if n == 1 {
return vec![local_strains[0]];
}
let h2 = element_size * element_size;
let alpha = self.c / h2;
let mut a_coeff = vec![0.0; n];
let mut b_coeff = vec![0.0; n];
let mut c_coeff = vec![0.0; n];
let mut d_vec = vec![0.0; n];
for i in 0..n {
b_coeff[i] = 1.0 + 2.0 * alpha;
d_vec[i] = local_strains[i];
if i > 0 {
a_coeff[i] = -alpha;
}
if i < n - 1 {
c_coeff[i] = -alpha;
}
}
b_coeff[0] = 1.0 + alpha;
b_coeff[n - 1] = 1.0 + alpha;
let mut cp = vec![0.0; n];
let mut dp = vec![0.0; n];
cp[0] = c_coeff[0] / b_coeff[0];
dp[0] = d_vec[0] / b_coeff[0];
for i in 1..n {
let m = b_coeff[i] - a_coeff[i] * cp[i - 1];
cp[i] = c_coeff[i] / m;
dp[i] = (d_vec[i] - a_coeff[i] * dp[i - 1]) / m;
}
let mut result = vec![0.0; n];
result[n - 1] = dp[n - 1];
for i in (0..n - 1).rev() {
result[i] = dp[i] - cp[i] * result[i + 1];
}
result
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct HashinFailureCriteria {
pub x_t: f64,
pub x_c: f64,
pub y_t: f64,
pub y_c: f64,
pub s12: f64,
}
impl HashinFailureCriteria {
pub fn new(x_t: f64, x_c: f64, y_t: f64, y_c: f64, s12: f64) -> Self {
Self {
x_t,
x_c,
y_t,
y_c,
s12,
}
}
pub fn fiber_tension(&self, sigma1: f64, tau12: f64, _tau13: f64) -> f64 {
if sigma1 <= 0.0 {
return 0.0;
}
(sigma1 / self.x_t).powi(2) + (tau12 / self.s12).powi(2)
}
pub fn fiber_compression(&self, sigma1: f64) -> f64 {
if sigma1 >= 0.0 {
return 0.0;
}
(sigma1 / self.x_c).powi(2)
}
pub fn matrix_tension(&self, sigma2: f64, tau12: f64) -> f64 {
if sigma2 <= 0.0 {
return 0.0;
}
(sigma2 / self.y_t).powi(2) + (tau12 / self.s12).powi(2)
}
pub fn matrix_compression(&self, sigma2: f64, tau12: f64) -> f64 {
if sigma2 >= 0.0 {
return 0.0;
}
let s23 = self.y_c / 2.0;
let t1 = (sigma2 / (2.0 * s23)).powi(2);
let t2 = ((self.y_c / (2.0 * s23)).powi(2) - 1.0) * (sigma2 / self.y_c);
let t3 = (tau12 / self.s12).powi(2);
t1 + t2 + t3
}
pub fn is_failed(&self, sigma1: f64, sigma2: f64, tau12: f64) -> bool {
self.fiber_tension(sigma1, tau12, 0.0) >= 1.0
|| self.fiber_compression(sigma1) >= 1.0
|| self.matrix_tension(sigma2, tau12) >= 1.0
|| self.matrix_compression(sigma2, tau12) >= 1.0
}
}
#[derive(Debug, Clone)]
pub struct NortonCreep {
pub a: f64,
pub n: f64,
pub q: f64,
pub r: f64,
}
impl NortonCreep {
pub fn new(a: f64, n: f64, q: f64) -> Self {
Self { a, n, q, r: 8.314 }
}
pub fn creep_rate(&self, sigma: f64, temperature: f64) -> f64 {
let sign = sigma.signum();
let abs_sigma = sigma.abs();
self.a * abs_sigma.powf(self.n) * sign * (-self.q / (self.r * temperature)).exp()
}
pub fn creep_increment(&self, sigma: f64, temperature: f64, dt: f64) -> f64 {
self.creep_rate(sigma, temperature) * dt
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct PuckFailureCriteria {
pub x_t: f64,
pub x_c: f64,
pub y_t: f64,
pub y_c: f64,
pub s12: f64,
}
impl PuckFailureCriteria {
#[allow(clippy::too_many_arguments)]
pub fn new(x_t: f64, x_c: f64, y_t: f64, y_c: f64, s12: f64) -> Self {
Self {
x_t,
x_c,
y_t,
y_c,
s12,
}
}
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.y_t * 0.01;
(sigma1 / self.x_t) * correction
}
pub fn fiber_failure_compression(&self, sigma1: f64) -> f64 {
if sigma1 >= 0.0 {
return 0.0;
}
(-sigma1) / self.x_c
}
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.s12).powi(2);
let factor = 1.0 - p_tt * self.y_t / self.s12;
let term_sig = (factor * sigma2 / self.y_t).powi(2);
(term_tau + term_sig).sqrt() + p_tt * sigma2 / self.s12
}
pub fn inter_fiber_failure_mode_bc(&self, sigma2: f64, tau12: f64, p_ct: f64) -> f64 {
if sigma2 >= 0.0 {
return 0.0;
}
let term_tau = (tau12 / self.s12 + p_ct * sigma2 / self.s12).powi(2);
let term_sig = (sigma2 / self.y_c).powi(2);
(term_tau + term_sig).sqrt()
}
pub fn max_failure_index(
&self,
sigma1: f64,
sigma2: f64,
tau12: f64,
e_f1: f64,
e_f2: f64,
) -> f64 {
let ff_t = self.fiber_failure_tension(sigma1, e_f1, sigma2, e_f2);
let ff_c = self.fiber_failure_compression(sigma1);
let iff_a = self.inter_fiber_failure_mode_a(sigma2, tau12, 0.25, 0.2);
let iff_bc = self.inter_fiber_failure_mode_bc(sigma2, tau12, 0.2);
ff_t.max(ff_c).max(iff_a).max(iff_bc)
}
}
#[derive(Debug, Clone)]
pub struct CrackBandModel {
pub gf: f64,
pub ft: f64,
pub young_modulus: f64,
pub h: f64,
pub d: f64,
pub kappa: f64,
}
impl CrackBandModel {
pub fn new(gf: f64, ft: f64, young_modulus: f64, h: f64) -> Self {
Self {
gf,
ft,
young_modulus,
h,
d: 0.0,
kappa: 0.0,
}
}
#[allow(dead_code)]
pub fn initiation_strain(&self) -> f64 {
self.ft / self.young_modulus
}
#[allow(dead_code)]
pub fn failure_strain(&self) -> f64 {
2.0 * self.gf / (self.ft * self.h)
}
#[allow(dead_code)]
pub fn has_snapback(&self) -> bool {
self.failure_strain() <= self.initiation_strain()
}
#[allow(dead_code)]
pub fn max_element_size(&self) -> f64 {
2.0 * self.young_modulus * self.gf / (self.ft * self.ft)
}
#[allow(dead_code)]
pub fn update(&mut self, strain: f64) {
if strain <= self.kappa {
return;
}
self.kappa = strain;
let eps0 = self.initiation_strain();
let eps_f = self.failure_strain();
if strain <= eps0 {
return;
}
let d_new = if strain >= eps_f {
1.0
} else {
eps_f / strain * (strain - eps0) / (eps_f - eps0)
};
self.d = d_new.clamp(self.d, 1.0);
}
#[allow(dead_code)]
pub fn stress(&self, strain: f64) -> f64 {
(1.0 - self.d) * self.young_modulus * strain
}
#[allow(dead_code)]
pub fn dissipated_energy_density(&self) -> f64 {
let eps0 = self.initiation_strain();
let eps_f = self.failure_strain();
0.5 * self.ft * (eps_f - eps0) * self.d
}
}
#[derive(Debug, Clone)]
pub struct GursonDamage {
pub f: f64,
pub f0: f64,
pub fc: f64,
pub f_f: f64,
pub q1: f64,
pub q2: f64,
pub q3: f64,
pub a_n: f64,
pub eps_n: f64,
pub s_n: f64,
}
impl GursonDamage {
#[allow(clippy::too_many_arguments)]
pub fn new(f0: f64, fc: f64, f_f: f64, a_n: f64, eps_n: f64, s_n: f64) -> Self {
let q1 = 1.5;
let q2 = 1.0;
Self {
f: f0,
f0,
fc,
f_f,
q1,
q2,
q3: q1 * q1,
a_n,
eps_n,
s_n,
}
}
#[allow(dead_code)]
pub fn effective_void_fraction(&self) -> f64 {
if self.f <= self.fc {
self.f
} else {
let f_bar_f = 1.0 / self.q1;
let kappa = (f_bar_f - self.fc) / (self.f_f - self.fc);
self.fc + kappa * (self.f - self.fc)
}
}
#[allow(dead_code)]
pub fn yield_function(&self, sigma_eq: f64, sigma_h: f64, sigma_y: f64) -> f64 {
let f_star = self.effective_void_fraction();
let term1 = (sigma_eq / sigma_y).powi(2);
let term2 = 2.0 * self.q1 * f_star * (1.5 * self.q2 * sigma_h / sigma_y).cosh();
let term3 = 1.0 + self.q3 * f_star * f_star;
term1 + term2 - term3
}
#[allow(dead_code)]
pub fn nucleation_rate(&self, eps_p: f64, eps_p_dot: f64) -> f64 {
let z = (eps_p - self.eps_n) / self.s_n;
let coeff = self.a_n / (self.s_n * (2.0 * std::f64::consts::PI).sqrt());
coeff * (-0.5 * z * z).exp() * eps_p_dot
}
#[allow(dead_code)]
pub fn growth_rate(&self, plastic_volumetric_strain_rate: f64) -> f64 {
(1.0 - self.f) * plastic_volumetric_strain_rate
}
#[allow(dead_code)]
pub fn update(
&mut self,
eps_p: f64,
eps_p_dot: f64,
plastic_volumetric_strain_rate: f64,
dt: f64,
) {
let f_nucleation = self.nucleation_rate(eps_p, eps_p_dot) * dt;
let f_growth = self.growth_rate(plastic_volumetric_strain_rate) * dt;
self.f = (self.f + f_nucleation + f_growth).clamp(0.0, self.f_f);
}
#[allow(dead_code)]
pub fn is_failed(&self) -> bool {
self.f >= self.f_f - 1e-15
}
#[allow(clippy::too_many_arguments)]
pub fn compute_void_growth_rate(
&self,
sigma_h: f64,
sigma_eq: f64,
sigma_y: f64,
deps_p: f64,
) -> f64 {
let triaxiality = if sigma_eq.abs() > 1e-30 {
sigma_h / sigma_eq
} else {
0.0
};
let a_rt = 0.283_f64;
let b_rt = 1.5_f64;
let growth = (1.0 - self.f) * a_rt * (b_rt * triaxiality).sinh() * deps_p;
let eps_p_effective = if sigma_eq.abs() > 1e-30 {
deps_p * sigma_y / sigma_eq
} else {
deps_p
};
let nucleation = self.nucleation_rate(eps_p_effective, 1.0) * deps_p;
(growth + nucleation).max(0.0)
}
}
#[derive(Debug, Clone)]
pub struct MinerFatigueDamage {
pub d: f64,
pub c: f64,
pub m: f64,
}
impl MinerFatigueDamage {
pub fn new(c: f64, m: f64) -> Self {
Self { d: 0.0, c, m }
}
#[allow(dead_code)]
pub fn fatigue_life(&self, stress_amplitude: f64) -> f64 {
if stress_amplitude.abs() < f64::EPSILON {
return f64::INFINITY;
}
self.c / stress_amplitude.abs().powf(self.m)
}
#[allow(dead_code)]
pub fn accumulate(&mut self, stress_amplitude: f64, n_cycles: f64) {
let n_f = self.fatigue_life(stress_amplitude);
if n_f.is_finite() {
self.d = (self.d + n_cycles / n_f).min(1.0);
}
}
#[allow(dead_code)]
pub fn is_failed(&self) -> bool {
self.d >= 1.0 - 1e-15
}
}