use super::functions::*;
pub struct ThermallyActivatedCreep {
pub nu0: f64,
pub delta_g: f64,
pub activation_volume: f64,
pub k_b: f64,
}
impl ThermallyActivatedCreep {
pub fn new(nu0: f64, delta_g: f64, activation_volume: f64) -> Self {
Self {
nu0,
delta_g,
activation_volume,
k_b: 1.380_649e-23,
}
}
pub fn strain_rate(&self, stress: f64, temperature: f64) -> f64 {
let kt = self.k_b * temperature;
let boltzmann = (-self.delta_g / kt).exp();
let sinh_term = (self.activation_volume * stress / kt).sinh();
self.nu0 * boltzmann * sinh_term
}
pub fn activation_stress(&self, strain_rate: f64, temperature: f64) -> f64 {
let kt = self.k_b * temperature;
let boltzmann = (-self.delta_g / kt).exp();
let arg = strain_rate / (self.nu0 * boltzmann);
(kt / self.activation_volume) * arg.asinh()
}
pub fn apparent_activation_energy(&self, stress: f64) -> f64 {
(self.delta_g - stress * self.activation_volume).max(0.0)
}
}
pub struct MonkmanGrant {
pub c_mg: f64,
pub m_exp: f64,
}
impl MonkmanGrant {
pub fn new(c_mg: f64, m_exp: f64) -> Self {
Self { c_mg, m_exp }
}
pub fn rupture_life(&self, steady_state_rate: f64) -> f64 {
if steady_state_rate <= 0.0 {
return f64::INFINITY;
}
self.c_mg / steady_state_rate.powf(self.m_exp)
}
pub fn steady_state_rate_from_life(&self, rupture_life: f64) -> f64 {
if rupture_life <= 0.0 {
return f64::INFINITY;
}
(self.c_mg / rupture_life).powf(1.0 / self.m_exp)
}
pub fn is_consistent(&self, steady_state_rate: f64, tolerance: f64) -> bool {
let t_f = self.rupture_life(steady_state_rate);
let rate_back = self.steady_state_rate_from_life(t_f);
(rate_back - steady_state_rate).abs() / steady_state_rate < tolerance
}
}
pub struct ChabocheKinematicHardening {
pub c: f64,
pub gamma: f64,
}
impl ChabocheKinematicHardening {
pub fn new(c: f64, gamma: f64) -> Self {
Self { c, gamma }
}
pub fn back_stress_rate(&self, plastic_strain_rate: f64, back_stress: f64) -> f64 {
self.c * plastic_strain_rate - self.gamma * back_stress * plastic_strain_rate.abs()
}
pub fn update_back_stress(&self, x: f64, dep: f64, dt: f64) -> f64 {
let rate = self.back_stress_rate(dep / dt.max(f64::EPSILON), x);
x + rate * dt
}
pub fn saturation_stress(&self) -> f64 {
if self.gamma.abs() > f64::EPSILON {
self.c / self.gamma
} else {
f64::INFINITY
}
}
}
#[allow(dead_code)]
pub struct PrimaryCreepModel {
pub a: f64,
pub n: f64,
pub m: f64,
}
impl PrimaryCreepModel {
pub fn new(a: f64, n: f64, m: f64) -> Self {
Self { a, n, m }
}
pub fn strain(&self, sigma: f64, t: f64) -> f64 {
self.a * sigma.powf(self.n) * t.powf(self.m)
}
pub fn strain_rate(&self, sigma: f64, t: f64) -> f64 {
if t <= 0.0 {
return f64::INFINITY;
}
self.a * sigma.powf(self.n) * self.m * t.powf(self.m - 1.0)
}
pub fn transition_time(&self, norton: &NortonCreep, sigma: f64, temperature: f64) -> f64 {
let eps_dot_ss = norton.creep_strain_rate(sigma, temperature);
if eps_dot_ss <= 0.0 || self.m >= 1.0 {
return f64::INFINITY;
}
let numerator = self.a * sigma.powf(self.n) * self.m;
(numerator / eps_dot_ss).powf(1.0 / (1.0 - self.m))
}
}
pub struct PowerLawCreep {
pub b: f64,
pub sigma_0: f64,
pub n: f64,
}
impl PowerLawCreep {
pub fn new(b: f64, sigma_0: f64, n: f64) -> Self {
Self { b, sigma_0, n }
}
pub fn strain_rate(&self, stress: f64) -> f64 {
self.b * (stress / self.sigma_0).powf(self.n)
}
pub fn strain_at_time(&self, stress: f64, t: f64) -> f64 {
self.strain_rate(stress) * t
}
pub fn creep_compliance(&self, t: f64) -> f64 {
self.b + self.sigma_0 * t.powf(self.n)
}
pub fn creep_rate(&self, t: f64) -> f64 {
if t <= 0.0 {
return f64::INFINITY;
}
self.sigma_0 * self.n * t.powf(self.n - 1.0)
}
pub fn creep_strain(&self, stress: f64, t: f64) -> f64 {
stress * self.creep_compliance(t)
}
}
pub struct CreepCurve {
pub primary_rate: f64,
pub secondary_rate: f64,
pub tertiary_start_strain: f64,
}
impl CreepCurve {
pub fn new(primary_rate: f64, secondary_rate: f64, tertiary_start_strain: f64) -> Self {
Self {
primary_rate,
secondary_rate,
tertiary_start_strain,
}
}
pub fn strain_at_time(&self, t: f64) -> f64 {
self.primary_rate * t.sqrt() + self.secondary_rate * t
}
pub fn strain_rate_at_time(&self, t: f64) -> f64 {
if t < 1e-30 {
return f64::INFINITY;
}
self.primary_rate / (2.0 * t.sqrt()) + self.secondary_rate
}
pub fn time_to_strain(&self, target_strain: f64) -> Option<f64> {
if target_strain <= 0.0 {
return Some(0.0);
}
let t_max = if self.secondary_rate > 0.0 {
target_strain / self.secondary_rate * 100.0
} else if self.primary_rate > 0.0 {
(target_strain / self.primary_rate).powi(2) * 10.0
} else {
return None;
};
if self.strain_at_time(t_max) < target_strain {
return None;
}
let mut lo = 0.0_f64;
let mut hi = t_max;
for _ in 0..64 {
let mid = 0.5 * (lo + hi);
if self.strain_at_time(mid) < target_strain {
lo = mid;
} else {
hi = mid;
}
}
Some(0.5 * (lo + hi))
}
pub fn stage_at_strain(&self, strain: f64) -> CreepStage {
if strain < self.primary_rate * 100.0_f64.sqrt() {
CreepStage::Primary { exponent: 0.5 }
} else if strain < self.tertiary_start_strain {
CreepStage::Secondary
} else {
CreepStage::Tertiary { acceleration: 2.0 }
}
}
}
pub struct AndradeCreep {
pub beta: f64,
pub k_steady: f64,
}
impl AndradeCreep {
pub fn new(beta: f64, k_steady: f64) -> Self {
Self { beta, k_steady }
}
pub fn strain(&self, t: f64) -> f64 {
if t <= 0.0 {
return 0.0;
}
self.beta * t.powf(1.0 / 3.0) + self.k_steady * t
}
pub fn strain_rate(&self, t: f64) -> f64 {
if t < 1e-30 {
return f64::INFINITY;
}
self.beta / (3.0 * t.powf(2.0 / 3.0)) + self.k_steady
}
pub fn transition_time(&self) -> f64 {
if self.k_steady <= 0.0 {
return f64::INFINITY;
}
(self.beta / self.k_steady).powf(1.5)
}
}
pub struct MansonHaferdParameter {
pub log10_ta: f64,
pub t_a: f64,
}
impl MansonHaferdParameter {
pub fn new(log10_ta: f64, t_a: f64) -> Self {
Self { log10_ta, t_a }
}
pub fn mh_parameter(&self, time_hours: f64, temperature: f64) -> f64 {
(time_hours.log10() - self.log10_ta) / (temperature - self.t_a)
}
pub fn rupture_time(&self, temperature: f64, p_mh: f64) -> f64 {
let log10_t = self.log10_ta + p_mh * (temperature - self.t_a);
10.0_f64.powf(log10_t)
}
pub fn max_temperature(&self, time_hours: f64, p_mh: f64) -> f64 {
let log10_t = time_hours.log10();
self.t_a + (log10_t - self.log10_ta) / p_mh
}
}
pub struct SherbyDornParameter {
pub activation_energy: f64,
}
impl SherbyDornParameter {
pub fn new(activation_energy: f64) -> Self {
Self { activation_energy }
}
pub fn parameter_constant_temp(&self, time_hours: f64, temperature: f64) -> f64 {
let arrhenius = (-self.activation_energy / (GAS_CONSTANT * temperature)).exp();
time_hours * arrhenius
}
pub fn parameter_variable_temp(&self, segments: &[(f64, f64)]) -> f64 {
segments
.iter()
.map(|&(dt, temp)| {
let arr = (-self.activation_energy / (GAS_CONSTANT * temp)).exp();
dt * arr
})
.sum()
}
pub fn equivalent_time(&self, p_sd: f64, temperature_ref: f64) -> f64 {
let arr = (-self.activation_energy / (GAS_CONSTANT * temperature_ref)).exp();
if arr <= 0.0 {
return f64::INFINITY;
}
p_sd / arr
}
}
pub struct CobleCreep {
pub d_gb0: f64,
pub activation_energy: f64,
pub gb_width: f64,
pub atomic_volume: f64,
pub grain_size: f64,
pub a_c: f64,
}
impl CobleCreep {
#[allow(clippy::too_many_arguments)]
pub fn new(
d_gb0: f64,
activation_energy: f64,
gb_width: f64,
atomic_volume: f64,
grain_size: f64,
) -> Self {
Self {
d_gb0,
activation_energy,
gb_width,
atomic_volume,
grain_size,
a_c: 50.0,
}
}
pub fn gb_diffusion_coefficient(&self, temperature: f64) -> f64 {
self.d_gb0 * (-self.activation_energy / (GAS_CONSTANT * temperature)).exp()
}
pub fn strain_rate(&self, stress: f64, temperature: f64) -> f64 {
let k_b = 1.380_649e-23;
let d_gb = self.gb_diffusion_coefficient(temperature);
self.a_c * d_gb * self.gb_width * stress * self.atomic_volume
/ (k_b * temperature * self.grain_size.powi(3))
}
}
impl CobleCreep {
pub fn gb_diffusivity(&self, temperature: f64) -> f64 {
self.d_gb0 * (-self.activation_energy / (GAS_CONSTANT * temperature)).exp()
}
pub fn coble_fraction(&self, nh: &NabarroHerringCreep, stress: f64, temperature: f64) -> f64 {
let rate_coble = self.strain_rate(stress, temperature);
let rate_nh = nh.strain_rate(stress, temperature);
let total = rate_coble + rate_nh;
if total > 0.0 { rate_coble / total } else { 0.5 }
}
}
pub struct NabarroHerringCreep {
pub d0: f64,
pub activation_energy: f64,
pub atomic_volume: f64,
pub grain_size: f64,
pub a_nh: f64,
}
impl NabarroHerringCreep {
pub fn new(d0: f64, activation_energy: f64, atomic_volume: f64, grain_size: f64) -> Self {
Self {
d0,
activation_energy,
atomic_volume,
grain_size,
a_nh: 14.0,
}
}
pub fn diffusion_coefficient(&self, temperature: f64) -> f64 {
self.d0 * (-self.activation_energy / (GAS_CONSTANT * temperature)).exp()
}
pub fn strain_rate(&self, stress: f64, temperature: f64) -> f64 {
let k_b = 1.380_649e-23;
let d_v = self.diffusion_coefficient(temperature);
self.a_nh * d_v * stress * self.atomic_volume
/ (k_b * temperature * self.grain_size * self.grain_size)
}
}
impl NabarroHerringCreep {
pub fn activation_energy(&self) -> f64 {
self.activation_energy
}
pub fn effective_diffusivity(&self, temperature: f64) -> f64 {
self.d0 * (-self.activation_energy / (GAS_CONSTANT * temperature)).exp()
}
pub fn compliance(&self, temperature: f64) -> f64 {
let d_eff = self.effective_diffusivity(temperature);
self.a_nh * d_eff * self.atomic_volume / (self.grain_size * self.grain_size)
}
}
#[allow(dead_code)]
pub struct TertiaryCreepModel {
pub a: f64,
pub n: f64,
pub b_damage: f64,
pub chi: f64,
pub phi: f64,
pub omega: f64,
}
impl TertiaryCreepModel {
#[allow(clippy::too_many_arguments)]
pub fn new(a: f64, n: f64, b_damage: f64, chi: f64, phi: f64) -> Self {
Self {
a,
n,
b_damage,
chi,
phi,
omega: 0.0,
}
}
pub fn strain_rate(&self, stress: f64) -> f64 {
let denom = (1.0 - self.omega).max(1e-9);
self.a * stress.powf(self.n) / denom.powf(self.n)
}
pub fn damage_rate(&self, stress: f64) -> f64 {
let denom = (1.0 - self.omega).max(1e-9);
self.b_damage * stress.powf(self.chi) / denom.powf(self.phi)
}
pub fn step(&mut self, stress: f64, dt: f64) {
let eps_dot = self.strain_rate(stress);
let omega_dot = self.damage_rate(stress);
let _ = eps_dot;
self.omega = (self.omega + omega_dot * dt).min(0.9999);
}
pub fn is_ruptured(&self) -> bool {
self.omega >= 0.999
}
pub fn rupture_time(&self, stress: f64) -> f64 {
let omega_dot = self.damage_rate(stress);
if omega_dot > 0.0 {
(1.0 - self.omega) / omega_dot
} else {
f64::INFINITY
}
}
pub fn reset(&mut self) {
self.omega = 0.0;
}
}
pub struct CreepDamage {
pub a: f64,
pub m: f64,
pub r: f64,
}
impl CreepDamage {
pub fn new(a: f64, m: f64, r: f64) -> Self {
Self { a, m, r }
}
pub fn damage_rate(&self, stress: f64, damage: f64) -> f64 {
let denominator = (1.0 - damage).powf(self.r);
if denominator < f64::EPSILON {
f64::INFINITY
} else {
self.a * stress.powf(self.m) / denominator
}
}
pub fn integrate(&self, stress: f64, dt: f64, n_steps: usize) -> Vec<f64> {
let mut omega = 0.0_f64;
let mut history = Vec::with_capacity(n_steps);
for _ in 0..n_steps {
let rate = self.damage_rate(stress, omega);
omega = (omega + rate * dt).min(1.0);
history.push(omega);
if omega >= 0.99 {
break;
}
}
history
}
pub fn time_to_failure(&self, stress: f64) -> f64 {
let base_rate = self.a * stress.powf(self.m);
if base_rate <= 0.0 {
return f64::INFINITY;
}
1.0 / ((self.r + 1.0) * base_rate)
}
pub fn effective_stress(&self, stress: f64, damage: f64) -> f64 {
if damage >= 1.0 {
f64::INFINITY
} else {
stress / (1.0 - damage)
}
}
}
pub struct LarsonMillerStress {
pub a_lm: f64,
pub b_lm: f64,
pub sigma_ref: f64,
pub c_lm: f64,
}
impl LarsonMillerStress {
pub fn new(a_lm: f64, b_lm: f64, sigma_ref: f64, c_lm: f64) -> Self {
Self {
a_lm,
b_lm,
sigma_ref,
c_lm,
}
}
pub fn lm_parameter(&self, sigma: f64) -> f64 {
if sigma <= 0.0 {
return f64::INFINITY;
}
self.a_lm - self.b_lm * (sigma / self.sigma_ref).log10()
}
pub fn rupture_time(&self, sigma: f64, temperature: f64) -> f64 {
let p = self.lm_parameter(sigma);
let log10_t = p / temperature - self.c_lm;
10.0_f64.powf(log10_t)
}
pub fn max_stress(&self, temperature: f64, time_hours: f64) -> f64 {
let p_required = temperature * (self.c_lm + time_hours.log10());
if self.b_lm.abs() < 1e-30 {
return self.sigma_ref;
}
let log_sigma_ratio = (self.a_lm - p_required) / self.b_lm;
self.sigma_ref * 10.0_f64.powf(log_sigma_ratio)
}
}
#[derive(Debug, Clone)]
pub struct RuptureEnvelope {
pub c: f64,
pub curve_coeffs: [f64; 3],
}
impl RuptureEnvelope {
pub fn new(c: f64, curve_coeffs: [f64; 3]) -> Self {
Self { c, curve_coeffs }
}
pub fn lm_parameter_from_stress(&self, stress: f64) -> f64 {
if stress <= 0.0 {
return 0.0;
}
let log_s = stress.log10();
self.curve_coeffs[0] + self.curve_coeffs[1] * log_s + self.curve_coeffs[2] * log_s * log_s
}
pub fn rupture_life(&self, stress: f64, temperature: f64) -> f64 {
let p = self.lm_parameter_from_stress(stress);
if temperature <= 0.0 {
return f64::INFINITY;
}
let exponent = p / temperature - self.c;
10.0_f64.powf(exponent)
}
pub fn allowable_stress(&self, temperature: f64, life_s: f64) -> f64 {
if life_s <= 0.0 || temperature <= 0.0 {
return 0.0;
}
let p_target = temperature * (self.c + life_s.log10());
let mut lo = 1.0e3_f64;
let mut hi = 1.0e9_f64;
let p_lo = self.lm_parameter_from_stress(lo);
let p_hi = self.lm_parameter_from_stress(hi);
if p_lo <= p_hi {
for _ in 0..80 {
let mid = (lo + hi) / 2.0;
if self.lm_parameter_from_stress(mid) < p_target {
lo = mid;
} else {
hi = mid;
}
}
} else {
for _ in 0..80 {
let mid = (lo + hi) / 2.0;
if self.lm_parameter_from_stress(mid) > p_target {
lo = mid;
} else {
hi = mid;
}
}
}
(lo + hi) / 2.0
}
}
#[allow(dead_code)]
pub struct ThreeStageCreep {
pub primary: PrimaryCreepModel,
pub secondary: SecondaryCreepModel,
pub tertiary: TertiaryCreepModel,
pub tertiary_onset_strain: f64,
pub strain: f64,
pub time: f64,
}
impl ThreeStageCreep {
pub fn new(
primary: PrimaryCreepModel,
secondary: SecondaryCreepModel,
tertiary: TertiaryCreepModel,
tertiary_onset_strain: f64,
) -> Self {
Self {
primary,
secondary,
tertiary,
tertiary_onset_strain,
strain: 0.0,
time: 0.0,
}
}
pub fn step(&mut self, stress: f64, temperature: f64, dt: f64) -> f64 {
let eps_dot_primary = self.primary.strain_rate(stress, self.time + dt);
let eps_dot_secondary = self.secondary.strain_rate(stress, temperature);
let eps_dot = if self.strain < self.tertiary_onset_strain {
eps_dot_primary.max(eps_dot_secondary)
} else {
self.tertiary.step(stress, dt);
self.tertiary.strain_rate(stress)
};
let d_eps = eps_dot * dt;
self.strain += d_eps;
self.time += dt;
d_eps
}
pub fn current_stage(&self) -> &'static str {
if self.tertiary.omega > 0.01 {
"tertiary"
} else if self.strain > self.tertiary_onset_strain * 0.5 {
"secondary"
} else {
"primary"
}
}
pub fn is_ruptured(&self) -> bool {
self.tertiary.is_ruptured()
}
}
#[derive(Debug, Clone)]
pub struct RuptureLifePrediction {
pub larson_miller: f64,
pub orr_sherby_dorn: f64,
pub monkman_grant: f64,
}
pub struct ThermalCreepIntegrator {
pub norton: NortonCreep,
}
impl ThermalCreepIntegrator {
pub fn new(norton: NortonCreep) -> Self {
Self { norton }
}
pub fn creep_strain_increment(&self, sigma: f64, temperature: f64, dt: f64) -> f64 {
self.norton.creep_strain_rate(sigma, temperature) * dt
}
pub fn integrate(&self, stress_history: &[(f64, f64)], temperature: f64, dt: f64) -> Vec<f64> {
let mut accumulated = 0.0_f64;
let mut strains = Vec::with_capacity(stress_history.len());
for &(_t, sigma) in stress_history {
accumulated += self.creep_strain_increment(sigma, temperature, dt);
strains.push(accumulated);
}
strains
}
}
pub struct StressRelaxation {
pub young_modulus: f64,
pub viscosity: f64,
}
impl StressRelaxation {
pub fn new(young_modulus: f64, viscosity: f64) -> Self {
Self {
young_modulus,
viscosity,
}
}
pub fn relaxation_time(&self) -> f64 {
self.viscosity / self.young_modulus
}
pub fn stress_at_time(&self, sigma_0: f64, t: f64) -> f64 {
let tau = self.relaxation_time();
sigma_0 * (-t / tau).exp()
}
pub fn time_to_fraction(&self, fraction: f64) -> f64 {
let tau = self.relaxation_time();
-tau * fraction.ln()
}
pub fn stress_history(&self, sigma_0: f64, t_end: f64, n_steps: usize) -> Vec<(f64, f64)> {
(0..=n_steps)
.map(|i| {
let t = t_end * (i as f64) / (n_steps as f64);
(t, self.stress_at_time(sigma_0, t))
})
.collect()
}
}
pub struct CreepFatigueInteraction {
pub creep_intercept: f64,
pub fatigue_intercept: f64,
pub interaction_exponent: f64,
}
impl CreepFatigueInteraction {
pub fn new(creep_intercept: f64, fatigue_intercept: f64, interaction_exponent: f64) -> Self {
Self {
creep_intercept,
fatigue_intercept,
interaction_exponent,
}
}
pub fn is_safe(&self, fatigue_damage: f64, creep_damage: f64) -> bool {
let n = self.interaction_exponent;
let term_f = (fatigue_damage / self.fatigue_intercept).powf(n);
let term_c = (creep_damage / self.creep_intercept).powf(n);
term_f + term_c <= 1.0
}
pub fn interaction_damage(&self, fatigue_damage: f64, creep_damage: f64) -> f64 {
let n = self.interaction_exponent;
let term_f = (fatigue_damage / self.fatigue_intercept).powf(n);
let term_c = (creep_damage / self.creep_intercept).powf(n);
(term_f + term_c).powf(1.0 / n)
}
pub fn remaining_creep(&self, fatigue_damage: f64) -> f64 {
let n = self.interaction_exponent;
let term_f = (fatigue_damage / self.fatigue_intercept).powf(n);
let remaining = (1.0 - term_f).max(0.0);
self.creep_intercept * remaining.powf(1.0 / n)
}
}
pub struct MultiaxialCreep {
pub norton: NortonCreep,
}
impl MultiaxialCreep {
pub fn new(norton: NortonCreep) -> Self {
Self { norton }
}
pub fn von_mises_stress(&self, s1: f64, s2: f64, s3: f64) -> f64 {
(0.5 * ((s1 - s2).powi(2) + (s2 - s3).powi(2) + (s3 - s1).powi(2))).sqrt()
}
pub fn mean_stress(&self, s1: f64, s2: f64, s3: f64) -> f64 {
(s1 + s2 + s3) / 3.0
}
pub fn principal_creep_rates(&self, s1: f64, s2: f64, s3: f64, temperature: f64) -> [f64; 3] {
let s_eq = self.von_mises_stress(s1, s2, s3);
if s_eq < 1e-30 {
return [0.0; 3];
}
let s_mean = self.mean_stress(s1, s2, s3);
let eps_dot_eq = self.norton.creep_strain_rate(s_eq, temperature);
let scale = 1.5 * eps_dot_eq / s_eq;
[
scale * (s1 - s_mean),
scale * (s2 - s_mean),
scale * (s3 - s_mean),
]
}
pub fn volumetric_creep_rate(&self, s1: f64, s2: f64, s3: f64, temperature: f64) -> f64 {
let rates = self.principal_creep_rates(s1, s2, s3, temperature);
rates[0] + rates[1] + rates[2]
}
}
#[derive(Debug, Clone)]
pub struct DeformationMechanismMap {
pub t_melting: f64,
pub shear_modulus: f64,
pub peierls_stress_norm: f64,
pub power_law_diff_boundary: f64,
}
impl DeformationMechanismMap {
pub fn new(
t_melting: f64,
shear_modulus: f64,
peierls_stress_norm: f64,
power_law_diff_boundary: f64,
) -> Self {
Self {
t_melting,
shear_modulus,
peierls_stress_norm,
power_law_diff_boundary,
}
}
pub fn nickel() -> Self {
Self::new(1728.0, 76.0e9, 3e-3, 1e-4)
}
pub fn dominant_mechanism(&self, stress: f64, temperature: f64) -> &'static str {
let t_hom = temperature / self.t_melting;
let sigma_norm = stress / self.shear_modulus;
if sigma_norm > self.peierls_stress_norm {
"dislocation_glide"
} else if t_hom < 0.3 {
"elastic"
} else if sigma_norm > self.power_law_diff_boundary {
"power_law_creep"
} else if t_hom > 0.85 {
"superplastic"
} else {
"diffusion_creep"
}
}
pub fn combined_strain_rate(
&self,
stress: f64,
temperature: f64,
a_pl: f64,
q_pl: f64,
a_d: f64,
q_d: f64,
) -> f64 {
let r = 8.314;
let sigma_norm = stress / self.shear_modulus;
let pl = a_pl * sigma_norm.powi(5) * (-q_pl / (r * temperature)).exp();
let diff = a_d * sigma_norm * (-q_d / (r * temperature)).exp();
pl + diff
}
}
pub struct NortonCreep {
pub a: f64,
pub n: f64,
pub activation_energy: f64,
pub gas_constant: f64,
}
impl NortonCreep {
pub fn new(a: f64, n: f64, activation_energy: f64) -> Self {
Self {
a,
n,
activation_energy,
gas_constant: GAS_CONSTANT,
}
}
pub fn creep_strain_rate(&self, stress: f64, temperature: f64) -> f64 {
let arrhenius = (-self.activation_energy / (self.gas_constant * temperature)).exp();
self.a * stress.powf(self.n) * arrhenius
}
pub fn creep_strain_rate_normalized(
&self,
stress: f64,
yield_stress: f64,
temperature: f64,
) -> f64 {
let sigma_norm = stress / yield_stress;
let arrhenius = (-self.activation_energy / (self.gas_constant * temperature)).exp();
self.a * sigma_norm.powf(self.n) * arrhenius
}
pub fn time_to_rupture(&self, stress: f64, temperature: f64) -> f64 {
let rate = self.creep_strain_rate(stress, temperature);
if rate > 0.0 {
1.0 / rate
} else {
f64::INFINITY
}
}
pub fn creep_rate(&self, stress: f64, temperature: f64) -> f64 {
self.creep_strain_rate(stress, temperature)
}
pub fn effective_viscosity(&self, stress: f64, temperature: f64) -> f64 {
let rate = self.creep_strain_rate(stress, temperature);
if rate > 1e-30 {
stress / (3.0 * rate)
} else {
f64::INFINITY
}
}
}
pub enum CreepStage {
Primary {
exponent: f64,
},
Secondary,
Tertiary {
acceleration: f64,
},
}
pub struct OrrSherbyDornParameter {
pub q: f64,
}
impl OrrSherbyDornParameter {
pub fn new(q: f64) -> Self {
Self { q }
}
pub fn osd_parameter(&self, time_hours: f64, temperature: f64) -> f64 {
time_hours.log10() - self.q / (2.303 * GAS_CONSTANT * temperature)
}
pub fn rupture_time(&self, temperature: f64, p_osd: f64) -> f64 {
let log10_t = p_osd + self.q / (2.303 * GAS_CONSTANT * temperature);
10.0_f64.powf(log10_t)
}
}
pub struct CoupledCreepDamage {
pub a: f64,
pub n: f64,
pub q: f64,
pub b_damage: f64,
pub m_damage: f64,
pub phi_damage: f64,
}
impl CoupledCreepDamage {
#[allow(clippy::too_many_arguments)]
pub fn new(a: f64, n: f64, q: f64, b_damage: f64, m_damage: f64, phi_damage: f64) -> Self {
Self {
a,
n,
q,
b_damage,
m_damage,
phi_damage,
}
}
pub fn creep_rate(&self, stress: f64, temperature: f64, damage: f64) -> f64 {
let eff_stress = stress / (1.0 - damage).max(f64::EPSILON);
let arr = (-self.q / (GAS_CONSTANT * temperature)).exp();
self.a * eff_stress.powf(self.n) * arr
}
pub fn damage_rate(&self, stress: f64, damage: f64) -> f64 {
let denom = (1.0 - damage).powf(self.phi_damage);
if denom < f64::EPSILON {
f64::INFINITY
} else {
self.b_damage * stress.powf(self.m_damage) / denom
}
}
pub fn integrate(
&self,
stress: f64,
temperature: f64,
dt: f64,
n_steps: usize,
) -> (Vec<f64>, Vec<f64>) {
let mut eps = 0.0_f64;
let mut omega = 0.0_f64;
let mut eps_hist = Vec::with_capacity(n_steps + 1);
let mut dam_hist = Vec::with_capacity(n_steps + 1);
eps_hist.push(eps);
dam_hist.push(omega);
for _ in 0..n_steps {
let cr = self.creep_rate(stress, temperature, omega);
let dr = self.damage_rate(stress, omega);
eps += cr * dt;
omega = (omega + dr * dt).min(1.0);
eps_hist.push(eps);
dam_hist.push(omega);
if omega >= 0.99 {
break;
}
}
(eps_hist, dam_hist)
}
}
#[allow(dead_code)]
pub struct SecondaryCreepModel {
pub norton: NortonCreep,
}
impl SecondaryCreepModel {
pub fn new(a: f64, n: f64, activation_energy: f64) -> Self {
Self {
norton: NortonCreep::new(a, n, activation_energy),
}
}
pub fn strain_rate(&self, stress: f64, temperature: f64) -> f64 {
self.norton.creep_strain_rate(stress, temperature)
}
pub fn accumulated_strain(&self, stress: f64, temperature: f64, dt: f64) -> f64 {
self.strain_rate(stress, temperature) * dt
}
pub fn viscosity(&self, stress: f64, temperature: f64) -> f64 {
self.norton.effective_viscosity(stress, temperature)
}
}
pub struct ViscoplasticModel {
pub yield_stress: f64,
pub viscosity: f64,
pub hardening: f64,
}
impl ViscoplasticModel {
pub fn new(yield_stress: f64, viscosity: f64, hardening: f64) -> Self {
Self {
yield_stress,
viscosity,
hardening,
}
}
pub fn overstress(&self, total_stress: f64, accumulated_plastic: f64) -> f64 {
let flow_stress = self.yield_stress + self.hardening * accumulated_plastic;
let excess = total_stress.abs() - flow_stress;
if excess > 0.0 {
excess / self.yield_stress
} else {
0.0
}
}
pub fn plastic_strain_rate(&self, stress: f64, plastic_strain: f64) -> f64 {
let phi = self.overstress(stress, plastic_strain);
phi / self.viscosity
}
pub fn step(&self, stress: f64, plastic_strain: f64, dt: f64) -> (f64, f64) {
let rate = self.plastic_strain_rate(stress, plastic_strain);
let dep = rate * dt * stress.signum();
let new_plastic = plastic_strain + dep;
let new_accumulated = plastic_strain.abs() + dep.abs();
(new_plastic, new_accumulated)
}
}
pub struct LarsonMillerParameter {
pub c_lm: f64,
}
impl LarsonMillerParameter {
pub fn new(c_lm: f64) -> Self {
Self { c_lm }
}
pub fn lm_parameter(&self, temperature: f64, time_hours: f64) -> f64 {
temperature * (self.c_lm + time_hours.log10())
}
pub fn rupture_time(&self, temperature: f64, lm_param: f64) -> f64 {
let log10_t = lm_param / temperature - self.c_lm;
10_f64.powf(log10_t)
}
pub fn max_temperature(&self, time_hours: f64, lm_param: f64) -> f64 {
lm_param / (self.c_lm + time_hours.log10())
}
pub fn equivalent_time(&self, t1: f64, temp1: f64, temp2: f64) -> f64 {
let p = self.lm_parameter(temp1, t1);
self.rupture_time(temp2, p)
}
}
pub struct ZenerHollomon {
pub activation_energy: f64,
}
impl ZenerHollomon {
pub fn new(activation_energy: f64) -> Self {
Self { activation_energy }
}
pub fn z_parameter(&self, strain_rate: f64, temperature: f64) -> f64 {
let arr = (self.activation_energy / (GAS_CONSTANT * temperature)).exp();
strain_rate * arr
}
pub fn equivalent_strain_rate(&self, z: f64, temperature: f64) -> f64 {
let arr = (-self.activation_energy / (GAS_CONSTANT * temperature)).exp();
z * arr
}
pub fn temperature_for_z(&self, z: f64, strain_rate: f64) -> f64 {
if strain_rate <= 0.0 || z <= 0.0 {
return f64::NAN;
}
self.activation_energy / (GAS_CONSTANT * (z / strain_rate).ln())
}
}
pub struct NortonBaileyTimeHardening {
pub a: f64,
pub n: f64,
pub m: f64,
}
impl NortonBaileyTimeHardening {
pub fn new(a: f64, n: f64, m: f64) -> Self {
Self { a, n, m }
}
pub fn strain(&self, sigma: f64, t: f64) -> f64 {
if t <= 0.0 {
return 0.0;
}
self.a * sigma.powf(self.n) * t.powf(self.m)
}
pub fn strain_rate(&self, sigma: f64, t: f64) -> f64 {
if t < 1e-30 {
return f64::INFINITY;
}
self.a * self.m * sigma.powf(self.n) * t.powf(self.m - 1.0)
}
pub fn equivalent_time(&self, accumulated_strain: f64, sigma_new: f64) -> f64 {
if accumulated_strain <= 0.0 || sigma_new <= 0.0 {
return 0.0;
}
let base = accumulated_strain / (self.a * sigma_new.powf(self.n));
base.powf(1.0 / self.m)
}
}