#[allow(unused_imports)]
use super::functions::*;
#[derive(Debug, Clone)]
pub struct KelvinVoigtChain {
pub elements: Vec<KelvinVoigtModel>,
}
impl KelvinVoigtChain {
pub fn new() -> Self {
Self {
elements: Vec::new(),
}
}
}
impl Default for KelvinVoigtChain {
fn default() -> Self {
Self::new()
}
}
impl KelvinVoigtChain {
pub fn add_element(&mut self, e: f64, eta: f64) {
self.elements.push(KelvinVoigtModel::new(e, eta));
}
pub fn creep_compliance(&self, t: f64) -> f64 {
self.elements.iter().map(|kv| kv.creep_compliance(t)).sum()
}
pub fn n_elements(&self) -> usize {
self.elements.len()
}
pub fn retardation_times(&self) -> Vec<f64> {
self.elements
.iter()
.map(|kv| kv.viscosity / kv.elastic_modulus)
.collect()
}
}
#[derive(Debug, Clone)]
pub struct FractionalSls {
pub e_inf: f64,
pub e1: f64,
pub springpot: SpringPot,
}
impl FractionalSls {
pub fn new(e_inf: f64, e1: f64, springpot: SpringPot) -> Self {
Self {
e_inf,
e1,
springpot,
}
}
pub fn relaxation_time(&self) -> f64 {
let alpha = self.springpot.alpha;
(self.springpot.quasi_property / self.e1).powf(1.0 / alpha)
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
let pi = std::f64::consts::PI;
let tau = self.relaxation_time();
let wt = (omega * tau).powf(self.springpot.alpha);
let cos_ap2 = (self.springpot.alpha * pi / 2.0).cos();
let denom = 1.0 + 2.0 * wt * cos_ap2 + wt * wt;
self.e_inf + self.e1 * wt * (wt + cos_ap2) / denom
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
let pi = std::f64::consts::PI;
let tau = self.relaxation_time();
let wt = (omega * tau).powf(self.springpot.alpha);
let cos_ap2 = (self.springpot.alpha * pi / 2.0).cos();
let sin_ap2 = (self.springpot.alpha * pi / 2.0).sin();
let denom = 1.0 + 2.0 * wt * cos_ap2 + wt * wt;
self.e1 * wt * sin_ap2 / denom
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
let ep = self.storage_modulus(omega);
let epp = self.loss_modulus(omega);
if ep.abs() < 1e-30 { 0.0 } else { epp / ep }
}
pub fn glassy_modulus(&self) -> f64 {
self.e_inf + self.e1
}
}
#[derive(Debug, Clone)]
pub struct PronySeries {
pub e_inf: f64,
pub terms: Vec<(f64, f64)>,
}
impl PronySeries {
pub fn new(e_inf: f64) -> Self {
Self {
e_inf,
terms: Vec::new(),
}
}
pub fn add_term(&mut self, g: f64, tau: f64) {
self.terms.push((g, tau));
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
let sum: f64 = self
.terms
.iter()
.map(|&(g, tau)| g * (-t / tau).exp())
.sum();
self.e_inf + sum
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
let sum: f64 = self
.terms
.iter()
.map(|&(g, tau)| {
let wt = omega * tau;
g * wt * wt / (1.0 + wt * wt)
})
.sum();
self.e_inf + sum
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
self.terms
.iter()
.map(|&(g, tau)| {
let wt = omega * tau;
g * wt / (1.0 + wt * wt)
})
.sum()
}
pub fn to_generalized_maxwell(&self) -> GeneralizedMaxwell {
let mut gm = GeneralizedMaxwell::new(self.e_inf);
for &(g, tau) in &self.terms {
gm.add_element(MaxwellModel::new(g, g * tau), 1.0);
}
gm
}
pub fn compute_relaxation_modulus(&self, t: f64) -> f64 {
let sum: f64 = self
.terms
.iter()
.map(|&(g, tau)| g * (-t / tau).exp())
.sum();
self.e_inf + sum
}
}
#[derive(Debug, Clone)]
pub struct SpringPot {
pub quasi_property: f64,
pub alpha: f64,
}
impl SpringPot {
pub fn new(quasi_property: f64, alpha: f64) -> Self {
debug_assert!((0.0..=1.0).contains(&alpha), "alpha must be in [0, 1]");
Self {
quasi_property,
alpha,
}
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
let pi = std::f64::consts::PI;
self.quasi_property * omega.powf(self.alpha) * (self.alpha * pi / 2.0).cos()
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
let pi = std::f64::consts::PI;
self.quasi_property * omega.powf(self.alpha) * (self.alpha * pi / 2.0).sin()
}
pub fn loss_tangent(&self) -> f64 {
let pi = std::f64::consts::PI;
(self.alpha * pi / 2.0).tan()
}
pub fn complex_modulus_magnitude(&self, omega: f64) -> f64 {
self.quasi_property * omega.powf(self.alpha)
}
pub fn phase_angle(&self) -> f64 {
self.alpha * std::f64::consts::FRAC_PI_2
}
}
#[derive(Debug, Clone)]
pub struct GeneralizedMaxwell {
pub spring_inf: f64,
pub maxwell_elements: Vec<MaxwellModel>,
pub weights: Vec<f64>,
}
impl GeneralizedMaxwell {
pub fn new(spring_inf: f64) -> Self {
Self {
spring_inf,
maxwell_elements: Vec::new(),
weights: Vec::new(),
}
}
pub fn add_element(&mut self, model: MaxwellModel, weight: f64) {
self.maxwell_elements.push(model);
self.weights.push(weight);
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
let sum: f64 = self
.maxwell_elements
.iter()
.zip(self.weights.iter())
.map(|(m, &w)| w * m.relaxation_modulus(t))
.sum();
self.spring_inf + sum
}
pub fn prony_coefficients(&self) -> Vec<(f64, f64)> {
self.maxwell_elements
.iter()
.zip(self.weights.iter())
.map(|(m, &w)| (m.elastic_modulus * w, m.relaxation_time()))
.collect()
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
let sum: f64 = self
.maxwell_elements
.iter()
.zip(self.weights.iter())
.map(|(m, &w)| {
let tau = m.relaxation_time();
let wt = omega * tau;
w * m.elastic_modulus * wt * wt / (1.0 + wt * wt)
})
.sum();
self.spring_inf + sum
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
self.maxwell_elements
.iter()
.zip(self.weights.iter())
.map(|(m, &w)| {
let tau = m.relaxation_time();
let wt = omega * tau;
w * m.elastic_modulus * wt / (1.0 + wt * wt)
})
.sum()
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
let ep = self.storage_modulus(omega);
let epp = self.loss_modulus(omega);
if ep.abs() < 1e-30 { 0.0 } else { epp / ep }
}
pub fn compute_tan_delta(&self, omega: f64) -> f64 {
let g_prime = self.storage_modulus(omega);
let g_double_prime = self.loss_modulus(omega);
if g_prime.abs() < 1e-30 {
f64::INFINITY
} else {
g_double_prime / g_prime
}
}
pub fn glassy_modulus(&self) -> f64 {
let sum: f64 = self
.maxwell_elements
.iter()
.zip(self.weights.iter())
.map(|(m, &w)| w * m.elastic_modulus)
.sum();
self.spring_inf + sum
}
pub fn n_elements(&self) -> usize {
self.maxwell_elements.len()
}
}
#[derive(Debug, Clone)]
pub struct WlfShift {
pub c1: f64,
pub c2: f64,
pub t_ref: f64,
}
impl WlfShift {
pub fn new(c1: f64, c2: f64, t_ref: f64) -> Self {
Self { c1, c2, t_ref }
}
pub fn log_shift_factor(&self, t: f64) -> f64 {
let dt = t - self.t_ref;
-self.c1 * dt / (self.c2 + dt)
}
pub fn shift_factor(&self, t: f64) -> f64 {
10.0_f64.powf(self.log_shift_factor(t))
}
pub fn shifted_frequency(&self, omega: f64, t: f64) -> f64 {
omega * self.shift_factor(t)
}
}
#[derive(Debug, Clone)]
pub struct MaxwellModel {
pub elastic_modulus: f64,
pub viscosity: f64,
}
impl MaxwellModel {
pub fn new(e: f64, eta: f64) -> Self {
Self {
elastic_modulus: e,
viscosity: eta,
}
}
pub fn relaxation_time(&self) -> f64 {
self.viscosity / self.elastic_modulus
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
let tau = self.relaxation_time();
self.elastic_modulus * (-t / tau).exp()
}
pub fn creep_compliance(&self, t: f64) -> f64 {
1.0 / self.elastic_modulus + t / self.viscosity
}
pub fn stress_update(&self, _epsilon: f64, epsilon_dot: f64, sigma_old: f64, dt: f64) -> f64 {
let tau = self.relaxation_time();
let exp_term = (-dt / tau).exp();
sigma_old * exp_term + self.elastic_modulus * epsilon_dot * tau * (1.0 - exp_term)
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
let tau = self.relaxation_time();
let wt = omega * tau;
self.elastic_modulus * wt * wt / (1.0 + wt * wt)
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
let tau = self.relaxation_time();
let wt = omega * tau;
self.elastic_modulus * wt / (1.0 + wt * wt)
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
let tau = self.relaxation_time();
1.0 / (omega * tau)
}
pub fn complex_modulus_magnitude(&self, omega: f64) -> f64 {
let ep = self.storage_modulus(omega);
let epp = self.loss_modulus(omega);
(ep * ep + epp * epp).sqrt()
}
pub fn compute_dynamic_modulus(&self, omega: f64) -> (f64, f64) {
let tau = self.relaxation_time();
let wt = omega * tau;
let denom = 1.0 + wt * wt;
let g_prime = self.elastic_modulus * wt * wt / denom;
let g_double_prime = self.elastic_modulus * wt / denom;
(g_prime, g_double_prime)
}
}
#[derive(Debug, Clone)]
pub struct PowerLawCreep {
pub j0: f64,
pub j1: f64,
pub n: f64,
}
impl PowerLawCreep {
pub fn new(j0: f64, j1: f64, n: f64) -> Self {
Self { j0, j1, n }
}
pub fn creep_compliance(&self, t: f64) -> f64 {
self.j0 + self.j1 * t.powf(self.n)
}
pub fn creep_strain(&self, sigma0: f64, t: f64) -> f64 {
sigma0 * self.creep_compliance(t)
}
pub fn creep_rate(&self, t: f64) -> f64 {
if t <= 0.0 {
return if self.n < 1.0 {
f64::INFINITY
} else {
self.j1 * self.n
};
}
self.j1 * self.n * t.powf(self.n - 1.0)
}
pub fn retardation_spectrum(&self, tau: f64) -> f64 {
if tau <= 0.0 {
return 0.0;
}
self.j1 * self.n * tau.powf(self.n - 1.0)
}
}
#[derive(Debug, Clone)]
pub struct KelvinVoigtModel {
pub elastic_modulus: f64,
pub viscosity: f64,
}
impl KelvinVoigtModel {
pub fn new(e: f64, eta: f64) -> Self {
Self {
elastic_modulus: e,
viscosity: eta,
}
}
pub fn creep_compliance(&self, t: f64) -> f64 {
(1.0 / self.elastic_modulus) * (1.0 - (-t * self.elastic_modulus / self.viscosity).exp())
}
pub fn relaxation_modulus(&self, _t: f64) -> f64 {
self.elastic_modulus
}
pub fn strain_update(&self, sigma: f64, epsilon_old: f64, dt: f64) -> f64 {
let exp_term = (-self.elastic_modulus * dt / self.viscosity).exp();
epsilon_old * exp_term + (sigma / self.elastic_modulus) * (1.0 - exp_term)
}
pub fn storage_modulus(&self, _omega: f64) -> f64 {
self.elastic_modulus
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
self.viscosity * omega
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
self.viscosity * omega / self.elastic_modulus
}
}
#[derive(Debug, Clone)]
pub struct KelvinVoigt {
pub young_modulus: f64,
pub viscosity: f64,
}
impl KelvinVoigt {
pub fn new(young_modulus: f64, viscosity: f64) -> Self {
Self {
young_modulus,
viscosity,
}
}
pub fn stress(&self, strain: f64, strain_rate: f64) -> f64 {
self.young_modulus * strain + self.viscosity * strain_rate
}
pub fn creep_strain(&self, sigma0: f64, t: f64) -> f64 {
let tau = self.relaxation_time();
(sigma0 / self.young_modulus) * (1.0 - (-t / tau).exp())
}
pub fn relaxation_time(&self) -> f64 {
self.viscosity / self.young_modulus
}
pub fn compute_creep_compliance(&self, t: f64) -> f64 {
let tau = self.relaxation_time();
(1.0 / self.young_modulus) * (1.0 - (-t / tau).exp())
}
}
#[derive(Debug, Clone)]
pub struct ArrheniusShift {
pub activation_energy: f64,
pub r: f64,
pub t_ref: f64,
}
impl ArrheniusShift {
pub fn new(activation_energy: f64, t_ref: f64) -> Self {
Self {
activation_energy,
r: 8.314,
t_ref,
}
}
pub fn ln_shift_factor(&self, t: f64) -> f64 {
(self.activation_energy / self.r) * (1.0 / t - 1.0 / self.t_ref)
}
pub fn shift_factor(&self, t: f64) -> f64 {
self.ln_shift_factor(t).exp()
}
pub fn shifted_frequency(&self, omega: f64, t: f64) -> f64 {
omega * self.shift_factor(t)
}
}
#[derive(Debug, Clone)]
pub struct NonlinearKelvinVoigt {
pub young_modulus: f64,
pub viscosity: f64,
pub m: f64,
}
impl NonlinearKelvinVoigt {
pub fn new(young_modulus: f64, viscosity: f64, m: f64) -> Self {
Self {
young_modulus,
viscosity,
m,
}
}
pub fn stress(&self, strain: f64, strain_rate: f64) -> f64 {
let sign = if strain_rate >= 0.0 { 1.0 } else { -1.0 };
self.young_modulus * strain + self.viscosity * sign * strain_rate.abs().powf(self.m)
}
pub fn linear_relaxation_time(&self) -> f64 {
self.viscosity / self.young_modulus
}
pub fn effective_dynamic_modulus(&self, omega: f64, epsilon0: f64) -> f64 {
let viscous_term = self.viscosity * (omega * epsilon0).powf(self.m - 1.0) * omega;
(self.young_modulus.powi(2) + viscous_term.powi(2)).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct Burgers {
pub e_maxwell: f64,
pub eta_maxwell: f64,
pub e_kv: f64,
pub eta_kv: f64,
}
impl Burgers {
pub fn new(e_maxwell: f64, eta_maxwell: f64, e_kv: f64, eta_kv: f64) -> Self {
Self {
e_maxwell,
eta_maxwell,
e_kv,
eta_kv,
}
}
pub fn retardation_time(&self) -> f64 {
self.eta_kv / self.e_kv
}
pub fn creep_compliance(&self, t: f64) -> f64 {
let tau_kv = self.retardation_time();
1.0 / self.e_maxwell
+ (1.0 / self.e_kv) * (1.0 - (-t / tau_kv).exp())
+ t / self.eta_maxwell
}
pub fn creep_strain(&self, sigma0: f64, t: f64) -> f64 {
sigma0 * self.creep_compliance(t)
}
pub fn instantaneous_compliance(&self) -> f64 {
1.0 / self.e_maxwell
}
pub fn steady_state_creep_rate(&self, sigma0: f64) -> f64 {
sigma0 / self.eta_maxwell
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
let tau_m = self.eta_maxwell / self.e_maxwell;
let wt_m = omega * tau_m;
let ep_maxwell = self.e_maxwell * wt_m * wt_m / (1.0 + wt_m * wt_m);
let ep_kv = self.e_kv;
if ep_maxwell < 1.0 {
return ep_kv;
}
1.0 / (1.0 / ep_maxwell + 1.0 / ep_kv)
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
let tau_m = self.eta_maxwell / self.e_maxwell;
let wt_m = omega * tau_m;
let epp_maxwell = self.e_maxwell * wt_m / (1.0 + wt_m * wt_m);
let epp_kv = self.eta_kv * omega;
epp_maxwell + epp_kv
}
}
#[derive(Debug, Clone)]
pub struct Kww {
pub e0: f64,
pub e_inf: f64,
pub tau: f64,
pub beta: f64,
}
impl Kww {
pub fn new(e0: f64, e_inf: f64, tau: f64, beta: f64) -> Self {
Self {
e0,
e_inf,
tau,
beta,
}
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
let phi = (-(t / self.tau).powf(self.beta)).exp();
(self.e0 - self.e_inf) * phi + self.e_inf
}
pub fn mean_relaxation_time(&self) -> f64 {
self.tau / self.beta * gamma_approx(1.0 / self.beta)
}
pub fn phi(&self, t: f64) -> f64 {
(-(t / self.tau).powf(self.beta)).exp()
}
pub fn half_relaxation_time(&self) -> f64 {
self.tau * (2.0_f64.ln()).powf(1.0 / self.beta)
}
}
#[derive(Debug, Clone)]
pub struct Maxwell {
pub young_modulus: f64,
pub viscosity: f64,
}
impl Maxwell {
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_relaxation(&self, epsilon0: f64, t: f64) -> f64 {
let tau = self.relaxation_time();
self.young_modulus * epsilon0 * (-t / tau).exp()
}
}
#[derive(Debug, Clone)]
pub struct StandardLinearSolid {
pub e1: f64,
pub e2: f64,
pub eta: f64,
}
impl StandardLinearSolid {
pub fn new(e1: f64, e2: f64, eta: f64) -> Self {
Self { e1, e2, eta }
}
pub fn relaxation_time(&self) -> f64 {
self.eta / self.e2
}
pub fn long_time_modulus(&self) -> f64 {
self.e1
}
pub fn short_time_modulus(&self) -> f64 {
self.e1 + self.e2
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
let tau = self.relaxation_time();
self.e1 + self.e2 * (-t / tau).exp()
}
pub fn creep_compliance(&self, t: f64) -> f64 {
let e_sum = self.e1 + self.e2;
let tau_c = self.eta * e_sum / (self.e1 * self.e2);
1.0 / e_sum + (self.e2 / (self.e1 * e_sum)) * (1.0 - (-t / tau_c).exp())
}
}
#[derive(Debug, Clone)]
pub struct MasterCurveBuilder {
pub t_ref: f64,
pub data: Vec<(f64, f64)>,
}
impl MasterCurveBuilder {
pub fn new(t_ref: f64) -> Self {
Self {
t_ref,
data: Vec::new(),
}
}
pub fn add_point_wlf(&mut self, wlf: &WlfShift, t_meas: f64, t_meas_time: f64, e_t: f64) {
let log_at = wlf.log_shift_factor(t_meas);
let log_t_reduced = t_meas_time.log10() + log_at;
self.data.push((log_t_reduced, e_t));
}
pub fn add_point_arrhenius(
&mut self,
arr: &ArrheniusShift,
t_meas: f64,
t_meas_time: f64,
e_t: f64,
) {
let ln_at = arr.ln_shift_factor(t_meas);
let log_at = ln_at / std::f64::consts::LN_10;
let log_t_reduced = t_meas_time.log10() + log_at;
self.data.push((log_t_reduced, e_t));
}
pub fn sort(&mut self) {
self.data
.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
}
pub fn interpolate(&self, log_t: f64) -> Option<f64> {
if self.data.len() < 2 {
return None;
}
for w in self.data.windows(2) {
let (x0, y0) = w[0];
let (x1, y1) = w[1];
if log_t >= x0 && log_t <= x1 {
let frac = (log_t - x0) / (x1 - x0);
return Some(y0 + frac * (y1 - y0));
}
}
None
}
pub fn n_points(&self) -> usize {
self.data.len()
}
}
#[derive(Debug, Clone)]
pub struct LogNormalRetardation {
pub j0: f64,
pub j_lm: f64,
pub tau_c: f64,
pub sigma: f64,
}
impl LogNormalRetardation {
pub fn new(j0: f64, j_lm: f64, tau_c: f64, sigma: f64) -> Self {
Self {
j0,
j_lm,
tau_c,
sigma,
}
}
pub fn spectrum(&self, tau: f64) -> f64 {
if tau <= 0.0 {
return 0.0;
}
let x = (tau / self.tau_c).ln() / self.sigma;
let pi = std::f64::consts::PI;
self.j_lm / (self.sigma * (2.0 * pi).sqrt()) * (-0.5 * x * x).exp()
}
pub fn creep_compliance(&self, t: f64) -> f64 {
let ln_lo = self.tau_c.ln() - 5.0 * self.sigma;
let ln_hi = self.tau_c.ln() + 5.0 * self.sigma;
let n = 200usize;
let d_ln = (ln_hi - ln_lo) / n as f64;
let integral: f64 = (0..n)
.map(|i| {
let ln_tau = ln_lo + (i as f64 + 0.5) * d_ln;
let tau = ln_tau.exp();
let l_tau = self.spectrum(tau);
l_tau * (1.0 - (-t / tau).exp()) * d_ln
})
.sum();
self.j0 + integral
}
pub fn storage_compliance(&self, omega: f64) -> f64 {
let ln_lo = self.tau_c.ln() - 5.0 * self.sigma;
let ln_hi = self.tau_c.ln() + 5.0 * self.sigma;
let n = 200usize;
let d_ln = (ln_hi - ln_lo) / n as f64;
let integral: f64 = (0..n)
.map(|i| {
let ln_tau = ln_lo + (i as f64 + 0.5) * d_ln;
let tau = ln_tau.exp();
let l_tau = self.spectrum(tau);
let wt2 = (omega * tau).powi(2);
l_tau / (1.0 + wt2) * d_ln
})
.sum();
self.j0 + integral
}
pub fn loss_compliance(&self, omega: f64) -> f64 {
let ln_lo = self.tau_c.ln() - 5.0 * self.sigma;
let ln_hi = self.tau_c.ln() + 5.0 * self.sigma;
let n = 200usize;
let d_ln = (ln_hi - ln_lo) / n as f64;
(0..n)
.map(|i| {
let ln_tau = ln_lo + (i as f64 + 0.5) * d_ln;
let tau = ln_tau.exp();
let l_tau = self.spectrum(tau);
let wt = omega * tau;
l_tau * wt / (1.0 + wt * wt) * d_ln
})
.sum()
}
}