use super::functions::*;
#[derive(Debug, Clone)]
pub struct DamageBandLocalization {
pub d: f64,
pub h_soft: f64,
pub e: f64,
pub nu: f64,
}
impl DamageBandLocalization {
pub fn new(d: f64, h_soft: f64, e: f64, nu: f64) -> Self {
Self { d, h_soft, e, nu }
}
pub fn localization_condition(&self) -> bool {
let e_t = (1.0 - self.d) * self.e - self.h_soft.abs();
e_t <= 0.0
}
pub fn process_zone_width(&self, l_visc_sq: f64) -> f64 {
use std::f64::consts::PI;
let h_abs = self.h_soft.abs().max(1e-30);
let lc = ((1.0 - self.d) * self.e * l_visc_sq / h_abs).sqrt();
lc * PI / 2.0
}
pub fn critical_wave_vector(&self) -> Option<f64> {
let g = self.e / (2.0 * (1.0 + self.nu));
let h_eff = (1.0 - self.d) * self.e - self.h_soft.abs();
if h_eff >= 0.0 {
return None;
}
Some((-h_eff / (2.0 * g)).sqrt())
}
}
#[derive(Debug, Clone)]
pub struct DamageVisualization {
pub damage_values: Vec<f64>,
pub colors: Vec<[f64; 3]>,
}
impl DamageVisualization {
pub fn from_states(states: &[DamageState]) -> Self {
let damage_values: Vec<f64> = states.iter().map(|s| s.d).collect();
let colors: Vec<[f64; 3]> = damage_values
.iter()
.map(|&d| Self::damage_to_color(d))
.collect();
Self {
damage_values,
colors,
}
}
pub fn damage_to_color(d: f64) -> [f64; 3] {
let d = d.clamp(0.0, 1.0);
if d < 0.5 {
let t = d * 2.0;
[t, t, 1.0 - t]
} else {
let t = (d - 0.5) * 2.0;
[1.0, 1.0 - t, 0.0]
}
}
pub fn min_damage(&self) -> f64 {
self.damage_values
.iter()
.copied()
.fold(f64::INFINITY, f64::min)
}
pub fn max_damage(&self) -> f64 {
self.damage_values.iter().copied().fold(0.0_f64, f64::max)
}
pub fn avg_damage(&self) -> f64 {
if self.damage_values.is_empty() {
return 0.0;
}
let sum: f64 = self.damage_values.iter().sum();
sum / self.damage_values.len() as f64
}
}
#[derive(Debug, Clone)]
pub struct DamageCreepCoupling {
pub a_creep: f64,
pub n_creep: f64,
pub m_creep: f64,
pub b_damage: f64,
pub r_damage: f64,
pub k_damage: f64,
}
impl DamageCreepCoupling {
pub fn new(
a_creep: f64,
n_creep: f64,
m_creep: f64,
b_damage: f64,
r_damage: f64,
k_damage: f64,
) -> Self {
Self {
a_creep,
n_creep,
m_creep,
b_damage,
r_damage,
k_damage,
}
}
pub fn creep_strain_rate(&self, sigma: f64, d: f64) -> f64 {
let omega = 1.0 - d.clamp(0.0, 1.0 - 1e-10);
self.a_creep * sigma.abs().powf(self.n_creep) / omega.powf(self.m_creep)
}
pub fn damage_rate(&self, sigma: f64, d: f64) -> f64 {
let omega = 1.0 - d.clamp(0.0, 1.0 - 1e-10);
self.b_damage * sigma.abs().powf(self.r_damage) / omega.powf(self.k_damage)
}
pub fn time_to_rupture(&self, sigma: f64) -> f64 {
let denom = self.b_damage * sigma.abs().powf(self.r_damage) * (self.k_damage + 1.0);
if denom < 1e-60 {
f64::INFINITY
} else {
1.0 / denom
}
}
pub fn explicit_step(&self, sigma: f64, epsilon_creep: f64, d: f64, dt: f64) -> (f64, f64) {
let deps_cr = self.creep_strain_rate(sigma, d) * dt;
let dd = self.damage_rate(sigma, d) * dt;
(epsilon_creep + deps_cr, (d + dd).clamp(0.0, 1.0))
}
}
#[derive(Debug, Clone)]
pub struct GursonModel {
pub f: f64,
pub q1: f64,
pub q2: f64,
pub q3: f64,
}
impl GursonModel {
pub fn yield_function(sigma_eq: f64, sigma_h: f64, sigma_0: f64) -> f64 {
let _ = (sigma_eq, sigma_h, sigma_0);
0.0
}
pub fn phi(&self, sigma_eq: f64, sigma_h: f64, sigma_0: f64) -> f64 {
if sigma_0.abs() < 1e-30 {
return f64::NAN;
}
let ratio = sigma_eq / sigma_0;
let cosh_term = (1.5 * self.q2 * sigma_h / sigma_0).cosh();
ratio * ratio + 2.0 * self.q1 * self.f * cosh_term - (1.0 + self.q3 * self.f * self.f)
}
pub fn void_growth_rate(&self, eps_p_vol: f64) -> f64 {
(1.0 - self.f) * eps_p_vol
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FailureMode {
Intact,
DamagingTension {
d: f64,
},
DamagingCompression {
d: f64,
},
FullyDamaged,
}
impl FailureMode {
pub fn classify(d: f64, alpha_t: f64) -> FailureMode {
if d <= 0.0 {
FailureMode::Intact
} else if d >= 0.99 {
FailureMode::FullyDamaged
} else if alpha_t >= 0.5 {
FailureMode::DamagingTension { d }
} else {
FailureMode::DamagingCompression { d }
}
}
pub fn is_damaged(&self) -> bool {
!matches!(self, FailureMode::Intact)
}
pub fn is_fully_damaged(&self) -> bool {
matches!(self, FailureMode::FullyDamaged)
}
pub fn damage_value(&self) -> f64 {
match self {
FailureMode::Intact => 0.0,
FailureMode::DamagingTension { d } => *d,
FailureMode::DamagingCompression { d } => *d,
FailureMode::FullyDamaged => 1.0,
}
}
}
#[derive(Debug, Clone)]
pub struct DamageHomogenization {
pub e_m: f64,
pub nu_m: f64,
pub crack_density: f64,
}
impl DamageHomogenization {
pub fn new(e_m: f64, nu_m: f64, crack_density: f64) -> Self {
Self {
e_m,
nu_m,
crack_density,
}
}
pub fn effective_youngs_modulus(&self) -> f64 {
let nu = self.nu_m;
let factor = 16.0 * (1.0 - nu * nu) / (9.0 * (1.0 - nu / 2.0));
self.e_m * (1.0 - self.crack_density * factor).max(0.0)
}
pub fn effective_shear_modulus(&self) -> f64 {
let nu = self.nu_m;
let g_m = self.e_m / (2.0 * (1.0 + nu));
let factor = 32.0 * (1.0 - nu) * (5.0 - nu) / (45.0 * (2.0 - nu));
g_m * (1.0 - self.crack_density * factor).max(0.0)
}
pub fn effective_bulk_modulus(&self) -> f64 {
let e_eff = self.effective_youngs_modulus();
let g_eff = self.effective_shear_modulus();
if g_eff.abs() < 1e-30 {
return 0.0;
}
e_eff * g_eff / (3.0 * (3.0 * g_eff - e_eff))
}
pub fn damage_from_crack_density(&self) -> f64 {
1.0 - self.effective_youngs_modulus() / self.e_m.max(1e-30)
}
}
#[derive(Debug, Clone)]
pub struct LemaitreCDM {
pub d: f64,
pub s: f64,
pub s0: f64,
}
impl LemaitreCDM {
pub fn evolution_rate(&self, eps_p: f64, sigma_eq: f64, sigma_h: f64) -> f64 {
if self.s.abs() < 1e-30 || sigma_eq.abs() < 1e-30 {
return 0.0;
}
let triax = sigma_h / sigma_eq;
let y = sigma_eq * sigma_eq / (2.0 * self.s) * (2.0 / 3.0 + 3.0 * triax * triax);
(y / self.s).powf(self.s0) * eps_p
}
}
#[derive(Debug, Clone)]
pub struct DamageMechanicsElement {
pub n_gauss_points: usize,
pub damage_states: Vec<DamageState>,
pub material_damage: IsotropicDamage,
pub deleted: bool,
pub element_id: usize,
}
impl DamageMechanicsElement {
pub fn new(n_gauss: usize, mat: IsotropicDamage) -> Self {
Self {
n_gauss_points: n_gauss,
damage_states: vec![DamageState::default(); n_gauss],
material_damage: mat,
deleted: false,
element_id: 0,
}
}
pub fn with_id(n_gauss: usize, mat: IsotropicDamage, id: usize) -> Self {
Self {
n_gauss_points: n_gauss,
damage_states: vec![DamageState::default(); n_gauss],
material_damage: mat,
deleted: false,
element_id: id,
}
}
pub fn update_all(&mut self, strains: &[[f64; 6]]) {
assert_eq!(
strains.len(),
self.n_gauss_points,
"strains slice length must match n_gauss_points"
);
for (state, strain) in self.damage_states.iter_mut().zip(strains.iter()) {
if state.deleted {
continue;
}
let old_d = state.d;
state.kappa = IsotropicDamage::update_kappa(state.kappa, strain);
state.d = self.material_damage.damage_variable(state.kappa);
state.damage_rate = (state.d - old_d).max(0.0);
}
}
pub fn update_all_limited(&mut self, strains: &[[f64; 6]], limiter: &DamageRateLimiter) {
assert_eq!(strains.len(), self.n_gauss_points);
for (state, strain) in self.damage_states.iter_mut().zip(strains.iter()) {
if state.deleted {
continue;
}
state.kappa = IsotropicDamage::update_kappa(state.kappa, strain);
let target_d = self.material_damage.damage_variable(state.kappa);
limiter.apply(state, target_d);
}
}
pub fn max_damage(&self) -> f64 {
self.damage_states
.iter()
.map(|s| s.d)
.fold(0.0_f64, f64::max)
}
pub fn avg_damage(&self) -> f64 {
let active: Vec<f64> = self
.damage_states
.iter()
.filter(|s| !s.deleted)
.map(|s| s.d)
.collect();
if active.is_empty() {
return 0.0;
}
active.iter().sum::<f64>() / active.len() as f64
}
pub fn is_failed(&self, threshold: f64) -> bool {
self.max_damage() >= threshold
}
pub fn check_deletion(&mut self, threshold: f64) {
if self.deleted {
return;
}
let all_failed = self.damage_states.iter().all(|s| s.d >= threshold);
if all_failed {
self.deleted = true;
for state in &mut self.damage_states {
state.deleted = true;
}
}
}
pub fn delete_failed_points(&mut self, threshold: f64) {
for state in &mut self.damage_states {
if state.d >= threshold {
state.deleted = true;
}
}
if self.damage_states.iter().all(|s| s.deleted) {
self.deleted = true;
}
}
pub fn deleted_point_count(&self) -> usize {
self.damage_states.iter().filter(|s| s.deleted).count()
}
pub fn active_point_count(&self) -> usize {
self.n_gauss_points - self.deleted_point_count()
}
pub fn visualization(&self) -> DamageVisualization {
DamageVisualization::from_states(&self.damage_states)
}
pub fn stiffness_reduction_factors(&self) -> Vec<f64> {
self.damage_states
.iter()
.map(|s| if s.deleted { 0.0 } else { 1.0 - s.d })
.collect()
}
}
#[derive(Debug, Clone, Copy)]
pub struct CoupledDamagePlasticityParams {
pub e: f64,
pub nu: f64,
pub sigma_y0: f64,
pub h: f64,
pub q: f64,
pub b_hard: f64,
pub c_kin: f64,
pub gamma_kin: f64,
pub s_dmg: f64,
pub s_dmg_exp: f64,
pub d_c: f64,
}
#[derive(Debug, Clone)]
pub struct CoupledDamagePlasticity {
pub e: f64,
pub nu: f64,
pub sigma_y0: f64,
pub h: f64,
pub q: f64,
pub b_hard: f64,
pub c_kin: f64,
pub gamma_kin: f64,
pub s_dmg: f64,
pub s_dmg_exp: f64,
pub d_c: f64,
}
impl CoupledDamagePlasticity {
pub fn new(p: CoupledDamagePlasticityParams) -> Self {
Self {
e: p.e,
nu: p.nu,
sigma_y0: p.sigma_y0,
h: p.h,
q: p.q,
b_hard: p.b_hard,
c_kin: p.c_kin,
gamma_kin: p.gamma_kin,
s_dmg: p.s_dmg,
s_dmg_exp: p.s_dmg_exp,
d_c: p.d_c,
}
}
pub fn yield_stress(&self, p_bar: f64) -> f64 {
self.sigma_y0 + self.h * p_bar + self.q * (1.0 - (-self.b_hard * p_bar).exp())
}
pub fn yield_function(&self, sigma_eff: &[f64; 6], back_stress: &[f64; 6], p_bar: f64) -> f64 {
let shifted: [f64; 6] = {
let mut s = [0.0; 6];
for i in 0..6 {
s[i] = sigma_eff[i] - back_stress[i];
}
s
};
von_mises_local(&shifted) - self.yield_stress(p_bar)
}
pub fn return_mapping(
&self,
state: &DamagePlasticityState,
sigma_trial: &[f64; 6],
) -> (DamagePlasticityState, bool) {
let d = state.d;
let factor = 1.0 / (1.0 - d).max(1e-12);
let sigma_eff_trial: [f64; 6] = {
let mut s = [0.0; 6];
for i in 0..6 {
s[i] = sigma_trial[i] * factor;
}
s
};
let f_trial = self.yield_function(&sigma_eff_trial, &state.back_stress, state.p_bar);
if f_trial <= 0.0 {
let mut new_state = state.clone();
new_state.effective_stress = sigma_eff_trial;
return (new_state, true);
}
let g = self.shear_modulus();
let n_eff = von_mises_local(&sigma_eff_trial).max(1e-30);
let n_vec: [f64; 6] = {
let mut n = [0.0; 6];
let s: [f64; 6] = deviatoric_stress(&sigma_eff_trial);
for i in 0..6 {
n[i] = 1.5 * s[i] / n_eff;
}
n
};
let mut dp = 0.0f64;
for _ in 0..50 {
let p_new = state.p_bar + dp;
let sy_new = self.yield_stress(p_new);
let res = n_eff - 3.0 * g * dp - sy_new;
if res.abs() < 1e-10 * self.sigma_y0 {
break;
}
let dsy = self.h + self.q * self.b_hard * (-self.b_hard * p_new).exp();
let slope = -3.0 * g - dsy;
if slope.abs() < 1e-30 {
break;
}
dp -= res / slope;
dp = dp.max(0.0);
}
let mut sigma_eff_new = [0.0f64; 6];
for i in 0..6 {
sigma_eff_new[i] = sigma_eff_trial[i] - 2.0 * g * dp * n_vec[i];
}
let mut back_new = state.back_stress;
for i in 0..6 {
back_new[i] += self.c_kin * dp * n_vec[i] - self.gamma_kin * back_new[i] * dp;
}
let sigma_eq = von_mises_local(&sigma_eff_new).max(1e-30);
let sigma_h = (sigma_eff_new[0] + sigma_eff_new[1] + sigma_eff_new[2]) / 3.0;
let triax = sigma_h / sigma_eq;
let g_mod = self.shear_modulus();
let nu = self.nu;
let rv = 2.0 / 3.0 * (1.0 + nu) + 3.0 * (1.0 - 2.0 * nu) * triax * triax;
let _ = g_mod;
let denom = 2.0 * self.e * (1.0 - d).powi(2).max(1e-30);
let y = sigma_eq * sigma_eq / denom * rv;
let dd = (y / self.s_dmg).powf(self.s_dmg_exp) * dp;
let p_new = state.p_bar + dp;
let new_d = (d + dd).min(self.d_c).clamp(0.0, 1.0);
let new_state = DamagePlasticityState {
d: new_d,
p_bar: p_new,
r: state.r + self.h * dp,
back_stress: back_new,
effective_stress: sigma_eff_new,
};
(new_state, true)
}
pub fn shear_modulus(&self) -> f64 {
self.e / (2.0 * (1.0 + self.nu))
}
}
#[derive(Debug, Clone)]
pub struct NonLocalDamage {
pub characteristic_length: f64,
}
impl NonLocalDamage {
pub fn new(characteristic_length: f64) -> Self {
Self {
characteristic_length,
}
}
pub fn weight(&self, distance: f64) -> f64 {
let lc = self.characteristic_length;
(-distance * distance / (2.0 * lc * lc)).exp()
}
pub fn nonlocal_strain(&self, local_strains: &[f64], distances: &[f64]) -> f64 {
assert_eq!(local_strains.len(), distances.len());
let mut weighted_sum = 0.0;
let mut weight_sum = 0.0;
for (&kappa, &dist) in local_strains.iter().zip(distances.iter()) {
let w = self.weight(dist);
weighted_sum += w * kappa;
weight_sum += w;
}
if weight_sum > 1e-30 {
weighted_sum / weight_sum
} else {
0.0
}
}
pub fn coupled_strain(&self, kappa_local: f64, kappa_nonlocal: f64, alpha: f64) -> f64 {
let alpha = alpha.clamp(0.0, 1.0);
alpha * kappa_local + (1.0 - alpha) * kappa_nonlocal
}
}
#[derive(Debug, Clone)]
pub struct DamageEvolutionLaw {
pub kappa0: f64,
pub alpha: f64,
pub beta: f64,
pub law_type: DamageLawType,
}
impl DamageEvolutionLaw {
pub fn exponential(kappa0: f64, alpha: f64, beta: f64) -> Self {
Self {
kappa0,
alpha,
beta,
law_type: DamageLawType::Exponential,
}
}
pub fn linear(kappa0: f64, kappa_u: f64) -> Self {
Self {
kappa0,
alpha: 1.0,
beta: kappa_u,
law_type: DamageLawType::Linear,
}
}
pub fn power_law(kappa0: f64, beta: f64) -> Self {
Self {
kappa0,
alpha: 1.0,
beta,
law_type: DamageLawType::PowerLaw,
}
}
pub fn compute_damage(&self, kappa: f64) -> f64 {
if kappa <= self.kappa0 {
return 0.0;
}
let d = match self.law_type {
DamageLawType::Exponential => {
1.0 - (self.kappa0 / kappa)
* (1.0 - self.alpha + self.alpha * (-self.beta * (kappa - self.kappa0)).exp())
}
DamageLawType::Linear => {
let kappa_u = self.beta;
if kappa >= kappa_u {
1.0
} else {
self.alpha * (kappa - self.kappa0) / (kappa_u - self.kappa0)
}
}
DamageLawType::PowerLaw => 1.0 - (self.kappa0 / kappa).powf(self.beta),
};
d.clamp(0.0, 1.0)
}
pub fn compute_damage_rate(&self, kappa: f64) -> f64 {
if kappa <= self.kappa0 {
return 0.0;
}
match self.law_type {
DamageLawType::Exponential => {
let ratio = self.kappa0 / kappa;
let exp_term = (-self.beta * (kappa - self.kappa0)).exp();
let part1 = ratio / kappa * (1.0 - self.alpha + self.alpha * exp_term);
let part2 = ratio * self.alpha * self.beta * exp_term;
part1 + part2
}
DamageLawType::Linear => {
let kappa_u = self.beta;
if kappa >= kappa_u {
0.0
} else {
self.alpha / (kappa_u - self.kappa0)
}
}
DamageLawType::PowerLaw => {
self.beta * self.kappa0.powf(self.beta) / kappa.powf(self.beta + 1.0)
}
}
}
pub fn update_state(&self, state: &DamageState, strain_equivalent: f64) -> DamageState {
let kappa_new = state.kappa.max(strain_equivalent);
let d_new = self.compute_damage(kappa_new).max(state.d);
DamageState {
d: d_new,
kappa: kappa_new,
plastic_strain: state.plastic_strain,
accumulated_plastic_strain: state.accumulated_plastic_strain,
damage_rate: d_new - state.d,
deleted: d_new >= 0.9999,
}
}
}
#[derive(Debug, Clone)]
pub struct DamagePlasticityState {
pub d: f64,
pub p_bar: f64,
pub r: f64,
pub back_stress: [f64; 6],
pub effective_stress: [f64; 6],
}
#[derive(Debug, Clone)]
pub struct FatigueDamageModel {
pub m0: f64,
pub beta: f64,
pub sigma_u: f64,
pub alpha: f64,
pub b: f64,
}
impl FatigueDamageModel {
pub fn new(m0: f64, beta: f64, sigma_u: f64, alpha: f64, b: f64) -> Self {
Self {
m0,
beta,
sigma_u,
alpha,
b,
}
}
pub fn damage_rate_per_cycle(&self, sigma_a: f64, sigma_mean: f64, d: f64) -> f64 {
if sigma_a <= self.sigma_u {
return 0.0;
}
let m_eff = self.m0 * (1.0 - self.b * sigma_mean / self.sigma_u).max(1e-12);
let base = (sigma_a / m_eff).powf(self.beta);
let d_clamped = d.clamp(0.0, 1.0 - 1e-10);
let factor = (1.0 - (1.0 - d_clamped).powf(self.beta + 1.0)).powf(self.alpha);
factor * base
}
pub fn remaining_life(
&self,
sigma_a: f64,
sigma_mean: f64,
d_current: f64,
n_steps: usize,
) -> f64 {
let mut d = d_current.clamp(0.0, 1.0 - 1e-10);
let dd = (1.0 - d) / n_steps as f64;
let mut total_cycles = 0.0f64;
for _ in 0..n_steps {
let rate = self.damage_rate_per_cycle(sigma_a, sigma_mean, d);
if rate < 1e-30 {
return f64::INFINITY;
}
total_cycles += dd / rate;
d += dd;
if d >= 1.0 {
break;
}
}
total_cycles
}
pub fn cycles_to_failure(&self, sigma_a: f64, sigma_mean: f64) -> f64 {
self.remaining_life(sigma_a, sigma_mean, 0.0, 1000)
}
}
#[derive(Debug, Clone)]
pub struct LemaitreDamage {
pub s: f64,
pub r: f64,
pub d_c: f64,
pub yield_stress: f64,
pub hardening: f64,
}
impl LemaitreDamage {
pub fn equivalent_strain(stress: &[f64; 6], d: f64, e_mod: f64, nu: f64) -> f64 {
let _ = nu;
let sigma_eq = von_mises(stress);
let denom = e_mod * (1.0 - d).max(1e-12);
sigma_eq / denom
}
pub fn damage_rate(&self, y: f64, dp: f64) -> f64 {
(y / self.s).powf(self.r) * dp
}
pub fn thermodynamic_force(stress: &[f64; 6], d: f64, e_mod: f64, nu: f64) -> f64 {
let sigma_eq = von_mises(stress).max(1e-30);
let sigma_h = hydrostatic(stress);
let triax = sigma_h / sigma_eq;
let rv = 2.0 / 3.0 * (1.0 + nu) + 3.0 * (1.0 - 2.0 * nu) * triax * triax;
let denom = 2.0 * e_mod * (1.0 - d).powi(2).max(1e-30);
sigma_eq * sigma_eq / denom * rv
}
pub fn update_damage(
&self,
state: &mut DamageState,
stress: &[f64; 6],
dp: f64,
e_mod: f64,
nu: f64,
) {
let y = Self::thermodynamic_force(stress, state.d, e_mod, nu);
let dd = self.damage_rate(y, dp);
state.damage_rate = dd;
state.d = (state.d + dd).min(self.d_c).clamp(0.0, 1.0);
state.accumulated_plastic_strain += dp;
}
pub fn update_damage_limited(
&self,
state: &mut DamageState,
stress: &[f64; 6],
dp: f64,
e_mod: f64,
nu: f64,
limiter: &DamageRateLimiter,
) {
let y = Self::thermodynamic_force(stress, state.d, e_mod, nu);
let dd = self.damage_rate(y, dp);
let dd_limited = limiter.limit(dd);
state.damage_rate = dd_limited;
state.d = (state.d + dd_limited).min(self.d_c).clamp(0.0, 1.0);
state.accumulated_plastic_strain += dp;
}
pub fn effective_stress(stress: &[f64; 6], d: f64) -> [f64; 6] {
let factor = 1.0 / (1.0 - d).max(1e-12);
let mut out = [0.0_f64; 6];
for i in 0..6 {
out[i] = stress[i] * factor;
}
out
}
pub fn damaged_stiffness(d: f64, c_intact: &[[f64; 6]; 6]) -> [[f64; 6]; 6] {
let factor = 1.0 - d;
let mut out = [[0.0_f64; 6]; 6];
for i in 0..6 {
for j in 0..6 {
out[i][j] = factor * c_intact[i][j];
}
}
out
}
}
#[derive(Debug, Clone)]
pub struct NonlocalContinuumDamage {
pub characteristic_length: f64,
pub epsilon_0: f64,
pub kappa_d: f64,
pub alpha_soft: f64,
}
impl NonlocalContinuumDamage {
pub fn new(characteristic_length: f64, epsilon_0: f64, kappa_d: f64, alpha_soft: f64) -> Self {
Self {
characteristic_length,
epsilon_0,
kappa_d,
alpha_soft,
}
}
pub fn gradient_parameter(&self) -> f64 {
self.characteristic_length * self.characteristic_length / 2.0
}
pub fn damage_loading_function(&self, kappa: f64, beta: f64) -> f64 {
if kappa <= self.epsilon_0 {
return 0.0;
}
let ratio = self.epsilon_0 / kappa;
let d = 1.0
- ratio
* ((1.0 - self.alpha_soft)
+ self.alpha_soft * (-(beta * (kappa - self.epsilon_0))).exp());
d.clamp(0.0, 1.0)
}
pub fn compute_nonlocal_strains(&self, positions: &[f64], local_strains: &[f64]) -> Vec<f64> {
assert_eq!(positions.len(), local_strains.len());
let c = self.gradient_parameter();
let lc = c.sqrt().max(1e-30);
let n = positions.len();
let mut result = vec![0.0f64; n];
for i in 0..n {
let mut num = 0.0f64;
let mut den = 0.0f64;
for j in 0..n {
let dist = (positions[i] - positions[j]).abs();
let w = (-dist / lc).exp() / (2.0 * lc);
num += w * local_strains[j];
den += w;
}
result[i] = if den > 1e-30 {
num / den
} else {
local_strains[i]
};
}
result
}
pub fn localization_indicator(&self, damage_profile: &[f64], dx: f64) -> f64 {
let threshold = 0.05;
let n_damaged = damage_profile.iter().filter(|&&d| d > threshold).count();
let zone_width = n_damaged as f64 * dx;
zone_width / self.characteristic_length
}
}
#[derive(Debug, Clone)]
pub struct ThermalDamage {
pub t_ref: f64,
pub t_melt: f64,
pub e0: f64,
pub m_exp: f64,
}
impl ThermalDamage {
pub fn new(t_ref: f64, t_melt: f64, e0: f64, m_exp: f64) -> Self {
Self {
t_ref,
t_melt,
e0,
m_exp,
}
}
pub fn stiffness_factor(&self, temperature: f64) -> f64 {
let theta = ((temperature - self.t_ref) / (self.t_melt - self.t_ref)).clamp(0.0, 1.0);
(1.0 - theta).powf(self.m_exp).max(0.0)
}
pub fn effective_modulus(&self, temperature: f64) -> f64 {
self.e0 * self.stiffness_factor(temperature)
}
pub fn thermal_damage_variable(&self, temperature: f64) -> f64 {
1.0 - self.stiffness_factor(temperature)
}
pub fn combined_damage(&self, d_mech: f64, temperature: f64) -> f64 {
let d_t = self.thermal_damage_variable(temperature);
(1.0 - (1.0 - d_mech) * (1.0 - d_t)).clamp(0.0, 1.0)
}
}
#[derive(Debug, Clone)]
pub struct CrackBandModel {
pub gf: f64,
pub fc: f64,
pub h: f64,
}
impl CrackBandModel {
pub fn softening_slope(&self) -> f64 {
-(self.fc * self.fc * self.h) / (2.0 * self.gf)
}
}
#[derive(Debug, Clone)]
pub struct IsotropicDamage {
pub threshold: f64,
pub a: f64,
}
impl IsotropicDamage {
pub fn equivalent_strain(strain: &[f64; 6]) -> f64 {
let pos = |x: f64| x.max(0.0);
let s = pos(strain[0]).powi(2) + pos(strain[1]).powi(2) + pos(strain[2]).powi(2);
s.sqrt()
}
pub fn damage_variable(&self, kappa: f64) -> f64 {
if kappa <= self.threshold {
return 0.0;
}
let d = 1.0 - (self.threshold / kappa) * (-(self.a * (kappa - self.threshold))).exp();
d.clamp(0.0, 1.0)
}
pub fn update_kappa(current_kappa: f64, current_strain: &[f64; 6]) -> f64 {
let eps_eq = Self::equivalent_strain(current_strain);
current_kappa.max(eps_eq)
}
pub fn damaged_stiffness(d: f64, c_intact: &[[f64; 6]; 6]) -> [[f64; 6]; 6] {
let factor = 1.0 - d;
let mut out = [[0.0_f64; 6]; 6];
for i in 0..6 {
for j in 0..6 {
out[i][j] = factor * c_intact[i][j];
}
}
out
}
pub fn stiffness_reduction_factor(&self, kappa: f64) -> f64 {
1.0 - self.damage_variable(kappa)
}
pub fn tangent_factor(&self, kappa: f64) -> f64 {
if kappa <= self.threshold {
return 1.0;
}
self.stiffness_reduction_factor(kappa)
}
}
#[derive(Debug, Clone)]
pub struct DamageRateLimiter {
pub max_rate: f64,
}
impl DamageRateLimiter {
pub fn new(max_rate: f64) -> Self {
Self { max_rate }
}
pub fn limit(&self, dd: f64) -> f64 {
dd.clamp(0.0, self.max_rate)
}
pub fn apply(&self, state: &mut DamageState, new_d: f64) {
let dd = (new_d - state.d).max(0.0);
let limited_dd = self.limit(dd);
state.damage_rate = limited_dd;
state.d = (state.d + limited_dd).clamp(0.0, 1.0);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DamageLawType {
Exponential,
Linear,
PowerLaw,
}
#[derive(Debug, Clone)]
pub struct MazarsDamage {
pub ac: f64,
pub bc: f64,
pub at: f64,
pub bt: f64,
pub kappa0: f64,
}
impl MazarsDamage {
pub fn equivalent_strain_mazars(strain: &[f64; 6]) -> f64 {
let pos = |x: f64| x.max(0.0);
let s = pos(strain[0]).powi(2) + pos(strain[1]).powi(2) + pos(strain[2]).powi(2);
s.sqrt()
}
pub fn damage_tension(&self, kappa: f64) -> f64 {
if kappa <= self.kappa0 {
return 0.0;
}
let d = 1.0
- self.kappa0 * (1.0 - self.at) / kappa
- self.at * (-(self.bt * (kappa - self.kappa0))).exp();
d.clamp(0.0, 1.0)
}
pub fn damage_compression(&self, kappa: f64) -> f64 {
if kappa <= self.kappa0 {
return 0.0;
}
let d = 1.0
- self.kappa0 * (1.0 - self.ac) / kappa
- self.ac * (-(self.bc * (kappa - self.kappa0))).exp();
d.clamp(0.0, 1.0)
}
pub fn update(&self, state: &mut DamageState, strain: &[f64; 6], alpha_t: f64) {
let eps_eq = Self::equivalent_strain_mazars(strain);
let kappa = eps_eq.max(state.kappa);
state.kappa = kappa;
if kappa <= self.kappa0 {
state.d = 0.0;
return;
}
let dt = self.damage_tension(kappa);
let dc = self.damage_compression(kappa);
let alpha_t = alpha_t.clamp(0.0, 1.0);
let new_d = (alpha_t * dt + (1.0 - alpha_t) * dc).clamp(0.0, 1.0);
state.damage_rate = (new_d - state.d).max(0.0);
state.d = new_d;
}
pub fn update_limited(
&self,
state: &mut DamageState,
strain: &[f64; 6],
alpha_t: f64,
limiter: &DamageRateLimiter,
) {
let eps_eq = Self::equivalent_strain_mazars(strain);
let kappa = eps_eq.max(state.kappa);
state.kappa = kappa;
if kappa <= self.kappa0 {
state.d = 0.0;
return;
}
let dt_val = self.damage_tension(kappa);
let dc_val = self.damage_compression(kappa);
let alpha_t = alpha_t.clamp(0.0, 1.0);
let target_d = (alpha_t * dt_val + (1.0 - alpha_t) * dc_val).clamp(0.0, 1.0);
limiter.apply(state, target_d);
}
}
#[derive(Debug, Clone)]
pub struct DamageState {
pub d: f64,
pub kappa: f64,
pub plastic_strain: [f64; 6],
pub accumulated_plastic_strain: f64,
pub damage_rate: f64,
pub deleted: bool,
}
#[derive(Debug, Clone)]
pub struct ElementDeletionManager {
pub deletion_threshold: f64,
pub deleted_count: usize,
pub deleted_ids: Vec<usize>,
}
impl ElementDeletionManager {
pub fn new(threshold: f64) -> Self {
Self {
deletion_threshold: threshold,
deleted_count: 0,
deleted_ids: Vec::new(),
}
}
pub fn process(&mut self, elements: &mut [DamageMechanicsElement]) {
for elem in elements.iter_mut() {
if elem.deleted {
continue;
}
elem.check_deletion(self.deletion_threshold);
if elem.deleted {
self.deleted_count += 1;
self.deleted_ids.push(elem.element_id);
}
}
}
pub fn deletion_ratio(&self, total_elements: usize) -> f64 {
if total_elements == 0 {
return 0.0;
}
self.deleted_count as f64 / total_elements as f64
}
}
#[derive(Debug, Clone)]
pub struct ScalarIsotropicDamage {
pub d: f64,
pub kappa: f64,
pub kappa_0: f64,
}
impl ScalarIsotropicDamage {
pub fn equivalent_strain(eps: &[f64]) -> f64 {
eps.iter().map(|&e| e * e).sum::<f64>().sqrt()
}
pub fn update(&mut self, eps_eq: f64) -> f64 {
if eps_eq > self.kappa {
self.kappa = eps_eq;
}
if self.kappa > self.kappa_0 {
let new_d = (1.0 - self.kappa_0 / self.kappa).clamp(0.0, 1.0);
if new_d > self.d {
self.d = new_d;
}
}
self.d
}
}
#[derive(Debug, Clone)]
pub struct LemaitreChabocheDamage {
pub s_strength: f64,
pub s: f64,
pub p_d: f64,
pub d_c: f64,
pub e: f64,
pub nu: f64,
}
impl LemaitreChabocheDamage {
pub fn triaxiality_factor(&self, sigma_eq: f64, sigma_h: f64) -> f64 {
if sigma_eq.abs() < 1e-30 {
return 1.0;
}
let triax = sigma_h / sigma_eq;
2.0 / 3.0 * (1.0 + self.nu) + 3.0 * (1.0 - 2.0 * self.nu) * triax * triax
}
pub fn damage_energy_release_rate(&self, sigma: &[f64; 6], d: f64) -> f64 {
let sigma_eq = von_mises_local(sigma).max(1e-30);
let sigma_h = (sigma[0] + sigma[1] + sigma[2]) / 3.0;
let rv = self.triaxiality_factor(sigma_eq, sigma_h);
let denom = 2.0 * self.e * (1.0 - d).powi(2).max(1e-30);
sigma_eq * sigma_eq * rv / denom
}
pub fn damage_increment(&self, sigma: &[f64; 6], d: f64, dp: f64, p_bar: f64) -> f64 {
if p_bar < self.p_d || dp <= 0.0 {
return 0.0;
}
let y = self.damage_energy_release_rate(sigma, d);
(y / self.s_strength).powf(self.s) * dp
}
pub fn update(&self, state: &mut DamageState, sigma: &[f64; 6], dp: f64) {
let dd = self.damage_increment(sigma, state.d, dp, state.accumulated_plastic_strain);
state.damage_rate = dd;
state.d = (state.d + dd).min(self.d_c).clamp(0.0, 1.0);
state.accumulated_plastic_strain += dp;
}
pub fn effective_stress(&self, sigma: &[f64; 6], d: f64) -> [f64; 6] {
let factor = 1.0 / (1.0 - d).max(1e-12);
let mut out = [0.0f64; 6];
for i in 0..6 {
out[i] = sigma[i] * factor;
}
out
}
pub fn is_ruptured(&self, d: f64) -> bool {
d >= self.d_c
}
pub fn closure_corrected_damage(&self, d: f64, sigma: &[f64; 6], h: f64) -> f64 {
let sigma_h = (sigma[0] + sigma[1] + sigma[2]) / 3.0;
if sigma_h >= 0.0 { d } else { h * d }
}
}