use super::functions::*;
#[derive(Debug, Clone)]
pub struct PronyTerm {
pub modulus: f64,
pub relaxation_time: f64,
}
#[derive(Debug, Clone)]
pub struct InternalVariable {
pub values: Vec<f64>,
pub n_terms: usize,
}
impl InternalVariable {
pub fn new(n_terms: usize) -> Self {
Self {
values: vec![0.0; n_terms],
n_terms,
}
}
pub fn update(&mut self, strain_increment: f64, material: &ViscoelasticMaterial, dt: f64) {
for (i, term) in material.prony_terms.iter().enumerate() {
let xi = (-dt / term.relaxation_time).exp();
let coeff = if dt.abs() < 1e-15 {
term.modulus
} else {
term.modulus * (1.0 - xi) * term.relaxation_time / dt
};
self.values[i] = self.values[i] * xi + coeff * strain_increment;
}
}
pub fn update_with_tts(
&mut self,
strain_increment: f64,
material: &ViscoelasticMaterial,
dt: f64,
shift_factor: f64,
) {
let dt_reduced = if shift_factor.abs() > 1e-30 {
dt / shift_factor
} else {
dt
};
self.update(strain_increment, material, dt_reduced);
}
pub fn total_stress(&self) -> f64 {
self.values.iter().sum()
}
pub fn reset(&mut self) {
for v in &mut self.values {
*v = 0.0;
}
}
}
#[derive(Debug, Clone)]
pub struct ShearProny {
pub g_inf: f64,
pub terms: Vec<(f64, f64)>,
}
impl ShearProny {
pub fn new(g_inf: f64, terms: Vec<(f64, f64)>) -> Self {
Self { g_inf, terms }
}
pub fn rubber() -> Self {
Self {
g_inf: 0.5e6,
terms: vec![(2.0e6, 0.01), (1.0e6, 0.1), (0.5e6, 1.0)],
}
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
self.g_inf
+ self
.terms
.iter()
.map(|(g_i, tau_i)| g_i * (-t / tau_i).exp())
.sum::<f64>()
}
pub fn instantaneous_modulus(&self) -> f64 {
self.g_inf + self.terms.iter().map(|(g_i, _)| g_i).sum::<f64>()
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
self.g_inf
+ self
.terms
.iter()
.map(|(g_i, tau_i)| {
let wt = omega * tau_i;
g_i * wt * wt / (1.0 + wt * wt)
})
.sum::<f64>()
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
self.terms
.iter()
.map(|(g_i, tau_i)| {
let wt = omega * tau_i;
g_i * wt / (1.0 + wt * wt)
})
.sum::<f64>()
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
let g_prime = self.storage_modulus(omega);
if g_prime.abs() < 1e-30 {
return 0.0;
}
self.loss_modulus(omega) / g_prime
}
pub fn peak_loss_frequency(&self) -> f64 {
if self.terms.is_empty() {
return 0.0;
}
let dominant = self
.terms
.iter()
.max_by(|(g1, _), (g2, _)| g1.partial_cmp(g2).unwrap_or(std::cmp::Ordering::Equal));
if let Some((_, tau)) = dominant {
if tau.abs() > 1e-30 { 1.0 / tau } else { 0.0 }
} else {
0.0
}
}
}
#[derive(Debug, Clone)]
pub struct ViscoelasticMaterial {
pub e_inf: f64,
pub prony_terms: Vec<PronyTerm>,
pub nu: f64,
}
impl ViscoelasticMaterial {
pub fn polymer() -> Self {
Self {
e_inf: 0.5e9,
prony_terms: vec![
PronyTerm {
modulus: 1.5e9,
relaxation_time: 1.0,
},
PronyTerm {
modulus: 0.8e9,
relaxation_time: 10.0,
},
PronyTerm {
modulus: 0.4e9,
relaxation_time: 100.0,
},
],
nu: 0.35,
}
}
pub fn rubber() -> Self {
Self {
e_inf: 1.0e6,
prony_terms: vec![
PronyTerm {
modulus: 5.0e6,
relaxation_time: 0.01,
},
PronyTerm {
modulus: 3.0e6,
relaxation_time: 0.1,
},
PronyTerm {
modulus: 1.0e6,
relaxation_time: 1.0,
},
PronyTerm {
modulus: 0.5e6,
relaxation_time: 10.0,
},
],
nu: 0.49,
}
}
pub fn instantaneous_modulus(&self) -> f64 {
self.e_inf + self.prony_terms.iter().map(|t| t.modulus).sum::<f64>()
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
self.e_inf
+ self
.prony_terms
.iter()
.map(|term| term.modulus * (-t / term.relaxation_time).exp())
.sum::<f64>()
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
self.prony_terms
.iter()
.map(|term| {
let wt = omega * term.relaxation_time;
term.modulus * wt / (1.0 + wt * wt)
})
.sum()
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
self.e_inf
+ self
.prony_terms
.iter()
.map(|term| {
let wt = omega * term.relaxation_time;
term.modulus * wt * wt / (1.0 + wt * wt)
})
.sum::<f64>()
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
let e_storage = self.storage_modulus(omega);
if e_storage.abs() < 1e-30 {
return 0.0;
}
self.loss_modulus(omega) / e_storage
}
pub fn num_terms(&self) -> usize {
self.prony_terms.len()
}
pub fn normalized_relaxation(&self, t: f64) -> f64 {
let e0 = self.instantaneous_modulus();
if e0.abs() < 1e-30 {
return 1.0;
}
self.relaxation_modulus(t) / e0
}
}
#[derive(Debug, Clone)]
pub struct KelvinVoigtModel {
pub modulus: f64,
pub viscosity: f64,
}
impl KelvinVoigtModel {
pub fn new(modulus: f64, viscosity: f64) -> Self {
Self { modulus, viscosity }
}
pub fn retardation_time(&self) -> f64 {
if self.modulus.abs() < 1e-30 {
return f64::INFINITY;
}
self.viscosity / self.modulus
}
pub fn creep_compliance(&self, t: f64) -> f64 {
if self.modulus.abs() < 1e-30 {
return 0.0;
}
let tau = self.retardation_time();
if tau.is_infinite() || tau.abs() < 1e-30 {
return 1.0 / self.modulus;
}
(1.0 / self.modulus) * (1.0 - (-t / tau).exp())
}
pub fn creep_strain(&self, sigma_0: f64, t: f64) -> f64 {
sigma_0 * self.creep_compliance(t)
}
pub fn instantaneous_stress(&self, epsilon: f64) -> f64 {
self.modulus * epsilon
}
pub fn storage_modulus(&self, _omega: f64) -> f64 {
self.modulus
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
self.viscosity * omega
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
if self.modulus.abs() < 1e-30 {
return f64::INFINITY;
}
self.viscosity * omega / self.modulus
}
}
#[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 equilibrium_modulus(&self) -> f64 {
let denom = self.e1 + self.e2;
if denom.abs() < 1e-30 {
return 0.0;
}
self.e1 * self.e2 / denom
}
pub fn instantaneous_modulus(&self) -> f64 {
self.e1
}
pub fn relaxation_time(&self) -> f64 {
let prod = self.e1 * self.e2;
if prod.abs() < 1e-30 {
return f64::INFINITY;
}
self.eta * (self.e1 + self.e2) / prod
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
let e_inf = self.equilibrium_modulus();
let e0 = self.instantaneous_modulus();
let tau_r = self.relaxation_time();
if tau_r.is_infinite() || tau_r.abs() < 1e-30 {
return e0;
}
e_inf + (e0 - e_inf) * (-t / tau_r).exp()
}
pub fn retardation_time(&self) -> f64 {
if self.e2.abs() < 1e-30 {
return f64::INFINITY;
}
self.eta / self.e2
}
pub fn creep_compliance(&self, t: f64) -> f64 {
let e_inf = self.equilibrium_modulus();
let e0 = self.instantaneous_modulus();
if e_inf.abs() < 1e-30 || e0.abs() < 1e-30 {
return 0.0;
}
let tau_c = self.retardation_time();
if tau_c.is_infinite() || tau_c.abs() < 1e-30 {
return 1.0 / e_inf;
}
1.0 / e_inf - (1.0 / e_inf - 1.0 / e0) * (-t / tau_c).exp()
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
let e_inf = self.equilibrium_modulus();
let e0 = self.instantaneous_modulus();
let tau_r = self.relaxation_time();
if tau_r.is_infinite() || tau_r.abs() < 1e-30 {
return e_inf;
}
let wt2 = (omega * tau_r).powi(2);
e_inf + (e0 - e_inf) * wt2 / (1.0 + wt2)
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
let e0 = self.instantaneous_modulus();
let e_inf = self.equilibrium_modulus();
let tau_r = self.relaxation_time();
if tau_r.is_infinite() || tau_r.abs() < 1e-30 {
return 0.0;
}
let wt = omega * tau_r;
(e0 - e_inf) * wt / (1.0 + wt * wt)
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
let e_prime = self.storage_modulus(omega);
if e_prime.abs() < 1e-30 {
return 0.0;
}
self.loss_modulus(omega) / e_prime
}
}
#[derive(Debug, Clone)]
pub struct ViscoelasticState {
pub stress: Vec<f64>,
pub internal_vars: Vec<Vec<f64>>,
}
impl ViscoelasticState {
pub fn new(n_stress: usize, n_prony: usize) -> Self {
Self {
stress: vec![0.0; n_stress],
internal_vars: vec![vec![0.0; n_stress]; n_prony],
}
}
}
#[derive(Debug)]
pub struct ViscoelasticBeam1D {
pub elements: Vec<ViscoelasticElement1D>,
pub n_nodes: usize,
}
impl ViscoelasticBeam1D {
pub fn new(n_elements: usize, length: f64, area: f64, material: ViscoelasticMaterial) -> Self {
let elem_len = length / n_elements as f64;
let elements = (0..n_elements)
.map(|_| ViscoelasticElement1D::new(elem_len, area, material.clone()))
.collect();
Self {
elements,
n_nodes: n_elements + 1,
}
}
pub fn assemble_stiffness(&self, dt: f64) -> Vec<Vec<f64>> {
let n = self.n_nodes;
let mut k = vec![vec![0.0; n]; n];
for (e, elem) in self.elements.iter().enumerate() {
let ke = elem.stiffness_matrix(dt);
for i in 0..2 {
for j in 0..2 {
k[e + i][e + j] += ke[i][j];
}
}
}
k
}
pub fn assemble_internal_forces(&self) -> Vec<f64> {
let n = self.n_nodes;
let mut f = vec![0.0; n];
for (e, elem) in self.elements.iter().enumerate() {
let fe = elem.internal_force_vector();
f[e] += fe[0];
f[e + 1] += fe[1];
}
f
}
pub fn apply_dirichlet(&self, k: &mut [Vec<f64>], f: &mut [f64], dofs: &[(usize, f64)]) {
let penalty = {
let max_k = k
.iter()
.flat_map(|row| row.iter())
.cloned()
.fold(0.0_f64, f64::max);
max_k * 1.0e14
};
for &(dof, val) in dofs {
k[dof][dof] += penalty;
f[dof] += penalty * val;
}
}
pub fn solve_step(&mut self, forces: &[f64], dt: f64) -> Vec<f64> {
let mut k = self.assemble_stiffness(dt);
let mut f = forces.to_vec();
self.apply_dirichlet(&mut k, &mut f, &[(0, 0.0)]);
let u = solve_tridiagonal_system(&k, &f);
for (e, elem) in self.elements.iter_mut().enumerate() {
let du = u[e + 1] - u[e];
let d_eps = du / elem.length;
elem.stress_update(d_eps, dt);
}
u
}
pub fn solve_step_with_temperature(
&mut self,
forces: &[f64],
dt: f64,
wlf: &WlfParameters,
temperature: f64,
) -> Vec<f64> {
let shift = wlf.shift_factor(temperature);
let dt_reduced = if shift.abs() > 1e-30 { dt / shift } else { dt };
let mut k = self.assemble_stiffness(dt_reduced);
let mut f = forces.to_vec();
self.apply_dirichlet(&mut k, &mut f, &[(0, 0.0)]);
let u = solve_tridiagonal_system(&k, &f);
for (e, elem) in self.elements.iter_mut().enumerate() {
let du = u[e + 1] - u[e];
let d_eps = du / elem.length;
elem.stress_update_with_tts(d_eps, dt, shift);
}
u
}
pub fn total_strain_energy(&self) -> f64 {
self.elements.iter().map(|e| e.strain_energy()).sum()
}
pub fn max_stress(&self) -> f64 {
self.elements
.iter()
.map(|e| e.stress().abs())
.fold(0.0f64, f64::max)
}
}
#[derive(Debug, Clone)]
pub struct ViscoelasticElement1D {
pub length: f64,
pub area: f64,
pub material: ViscoelasticMaterial,
pub internal_vars: InternalVariable,
pub(super) strain: f64,
pub(super) stress: f64,
}
impl ViscoelasticElement1D {
pub fn new(length: f64, area: f64, material: ViscoelasticMaterial) -> Self {
let n = material.prony_terms.len();
Self {
length,
area,
material,
internal_vars: InternalVariable::new(n),
strain: 0.0,
stress: 0.0,
}
}
pub fn algorithmic_modulus(&self, dt: f64) -> f64 {
let sum: f64 = self
.material
.prony_terms
.iter()
.map(|term| {
if dt.abs() < 1e-15 {
term.modulus
} else {
let xi = (-dt / term.relaxation_time).exp();
term.modulus * (1.0 - xi) * term.relaxation_time / dt
}
})
.sum();
self.material.e_inf + sum
}
pub fn stiffness_matrix(&self, dt: f64) -> [[f64; 2]; 2] {
let k = self.algorithmic_modulus(dt) * self.area / self.length;
[[k, -k], [-k, k]]
}
pub fn stress_update(&mut self, strain_increment: f64, dt: f64) -> f64 {
self.internal_vars
.update(strain_increment, &self.material, dt);
self.strain += strain_increment;
self.stress =
self.material.e_inf * self.strain + self.internal_vars.values.iter().sum::<f64>();
self.stress
}
pub fn stress_update_with_tts(
&mut self,
strain_increment: f64,
dt: f64,
shift_factor: f64,
) -> f64 {
self.internal_vars
.update_with_tts(strain_increment, &self.material, dt, shift_factor);
self.strain += strain_increment;
self.stress =
self.material.e_inf * self.strain + self.internal_vars.values.iter().sum::<f64>();
self.stress
}
pub fn strain(&self) -> f64 {
self.strain
}
pub fn stress(&self) -> f64 {
self.stress
}
pub fn strain_energy(&self) -> f64 {
0.5 * self.stress * self.strain * self.area * self.length
}
pub fn internal_force_vector(&self) -> [f64; 2] {
let f = self.stress * self.area;
[-f, f]
}
}
#[derive(Debug, Clone)]
pub struct WLFShift {
pub c1: f64,
pub c2: f64,
pub t_ref: f64,
}
impl WLFShift {
pub fn shift_factor(&self, temp: f64) -> f64 {
let dt = temp - self.t_ref;
let denom = self.c2 + dt;
if denom.abs() < 1e-30 {
return 0.0;
}
-self.c1 * dt / denom
}
}
#[derive(Debug, Clone)]
pub struct ArrheniusParameters {
pub t_ref: f64,
pub delta_h_over_r: f64,
}
impl ArrheniusParameters {
pub fn shift_factor(&self, temperature: f64) -> f64 {
let exponent = self.delta_h_over_r * (1.0 / temperature - 1.0 / self.t_ref);
exponent.exp()
}
}
#[derive(Debug, Clone)]
pub struct PronyRelaxation {
pub e_inf: f64,
pub elements: Vec<PronyElement>,
}
impl PronyRelaxation {
pub fn relaxation_modulus(&self, t: f64) -> f64 {
self.e_inf
+ self
.elements
.iter()
.map(|elem| elem.e_i * (-t / elem.tau_i).exp())
.sum::<f64>()
}
}
#[derive(Debug, Clone)]
pub struct MaxwellModel {
pub modulus: f64,
pub viscosity: f64,
}
impl MaxwellModel {
pub fn new(modulus: f64, viscosity: f64) -> Self {
Self { modulus, viscosity }
}
pub fn relaxation_time(&self) -> f64 {
if self.modulus.abs() < 1e-30 {
return f64::INFINITY;
}
self.viscosity / self.modulus
}
pub fn stress_relaxation(&self, epsilon_0: f64, t: f64) -> f64 {
let tau = self.relaxation_time();
if tau.is_infinite() || tau.abs() < 1e-30 {
return 0.0;
}
self.modulus * epsilon_0 * (-t / tau).exp()
}
pub fn relaxation_modulus(&self, t: f64) -> f64 {
let tau = self.relaxation_time();
if tau.is_infinite() || tau.abs() < 1e-30 {
return 0.0;
}
self.modulus * (-t / tau).exp()
}
pub fn creep_compliance(&self, t: f64) -> f64 {
let j0 = if self.modulus.abs() > 1e-30 {
1.0 / self.modulus
} else {
0.0
};
let j_creep = if self.viscosity.abs() > 1e-30 {
t / self.viscosity
} else {
0.0
};
j0 + j_creep
}
pub fn storage_modulus(&self, omega: f64) -> f64 {
let tau = self.relaxation_time();
if tau.is_infinite() || tau.abs() < 1e-30 {
return 0.0;
}
let wt = omega * tau;
self.modulus * wt * wt / (1.0 + wt * wt)
}
pub fn loss_modulus(&self, omega: f64) -> f64 {
let tau = self.relaxation_time();
if tau.is_infinite() || tau.abs() < 1e-30 {
return 0.0;
}
let wt = omega * tau;
self.modulus * wt / (1.0 + wt * wt)
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
let e_prime = self.storage_modulus(omega);
if e_prime.abs() < 1e-30 {
return f64::INFINITY;
}
self.loss_modulus(omega) / e_prime
}
}
#[derive(Debug, Clone)]
pub struct PronyElement {
pub e_i: f64,
pub tau_i: f64,
}
#[derive(Debug, Clone)]
pub struct PronyFemElement {
pub nodes: [usize; 4],
pub prony: PronyRelaxation,
pub state: ViscoelasticState,
pub dt: f64,
}
impl PronyFemElement {
pub fn new(nodes: [usize; 4], prony: PronyRelaxation, dt: f64) -> Self {
let n_prony = prony.elements.len();
let state = ViscoelasticState::new(1, n_prony);
Self {
nodes,
prony,
state,
dt,
}
}
pub fn update_stresses(&mut self, strain_inc: &[f64]) -> Vec<f64> {
let n_comp = strain_inc.len();
if self.state.stress.len() != n_comp {
self.state.stress = vec![0.0; n_comp];
}
if self.state.internal_vars.len() != self.prony.elements.len() {
self.state.internal_vars = vec![vec![0.0; n_comp]; self.prony.elements.len()];
}
for k in 0..self.prony.elements.len() {
if self.state.internal_vars[k].len() != n_comp {
self.state.internal_vars[k] = vec![0.0; n_comp];
}
}
let dt = self.dt;
for (k, elem) in self.prony.elements.iter().enumerate() {
let xi = (-dt / elem.tau_i).exp();
let coeff = if dt.abs() < 1e-15 {
elem.e_i
} else {
elem.e_i * (1.0 - xi) * elem.tau_i / dt
};
for (c, iv_c) in self.state.internal_vars[k].iter_mut().enumerate() {
*iv_c = *iv_c * xi + coeff * strain_inc[c];
}
}
for c in 0..n_comp {
let h_sum: f64 = self.state.internal_vars.iter().map(|iv| iv[c]).sum();
self.state.stress[c] += self.prony.e_inf * strain_inc[c] + h_sum
- self.state.internal_vars.iter().map(|iv| iv[c]).sum::<f64>()
+ h_sum;
}
for c in 0..n_comp {
self.state.stress[c] = self.prony.e_inf * strain_inc[c]
+ self.state.internal_vars.iter().map(|iv| iv[c]).sum::<f64>();
}
self.state.stress.clone()
}
}
#[derive(Debug, Clone)]
pub struct Creep {
pub j0: f64,
pub j_inf: f64,
pub tau: f64,
}
impl Creep {
pub fn compliance(&self, t: f64) -> f64 {
self.j0 + self.j_inf * (1.0 - (-t / self.tau).exp())
}
}
#[derive(Debug, Clone)]
pub struct WlfParameters {
pub t_ref: f64,
pub c1: f64,
pub c2: f64,
}
impl WlfParameters {
pub fn new(t_ref: f64, c1: f64, c2: f64) -> Self {
Self { t_ref, c1, c2 }
}
pub fn typical_polymer() -> Self {
Self {
t_ref: 25.0,
c1: 17.44,
c2: 51.6,
}
}
pub fn log_shift_factor(&self, temperature: f64) -> f64 {
let dt = temperature - self.t_ref;
let denom = self.c2 + dt;
if denom.abs() < 1e-10 {
return 0.0;
}
-self.c1 * dt / denom
}
pub fn shift_factor(&self, temperature: f64) -> f64 {
10.0_f64.powf(self.log_shift_factor(temperature))
}
pub fn reduced_time(&self, real_time: f64, temperature: f64) -> f64 {
let at = self.shift_factor(temperature);
if at.abs() < 1e-30 {
return real_time;
}
real_time / at
}
pub fn relaxation_modulus_at_temp(
&self,
material: &ViscoelasticMaterial,
time: f64,
temperature: f64,
) -> f64 {
let t_reduced = self.reduced_time(time, temperature);
material.relaxation_modulus(t_reduced)
}
}
#[derive(Debug, Clone)]
pub struct FrequencySweepPoint {
pub omega: f64,
pub storage: f64,
pub loss: f64,
pub tan_delta: f64,
pub magnitude: f64,
}