use super::functions::*;
#[derive(Debug, Clone)]
pub struct HolzapfelGasserOgden {
pub c1: f64,
pub k1: f64,
pub k2: f64,
pub kappa_disp: f64,
pub a1: [f64; 3],
pub a2: [f64; 3],
pub bulk_modulus: f64,
}
impl HolzapfelGasserOgden {
#[allow(clippy::too_many_arguments)]
pub fn new(
c1: f64,
k1: f64,
k2: f64,
kappa_disp: f64,
a1: [f64; 3],
a2: [f64; 3],
bulk_modulus: f64,
) -> Self {
Self {
c1,
k1,
k2,
kappa_disp,
a1,
a2,
bulk_modulus,
}
}
pub fn aorta_adventitia() -> Self {
Self::new(
7640.0,
996.6,
524.6,
0.226,
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
1.0e6,
)
}
pub fn aorta_media() -> Self {
Self::new(
3000.0,
2362.0,
100.7,
0.1,
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
1.0e6,
)
}
fn generalized_invariant(i1_bar: f64, i4_bar: f64, kappa_disp: f64) -> f64 {
kappa_disp * i1_bar + (1.0 - 3.0 * kappa_disp) * i4_bar
}
#[allow(clippy::needless_range_loop)]
fn isochoric_fiber_stretch_sq(f: &[[f64; 3]; 3], a: &[f64; 3]) -> f64 {
let j = det3(f);
let j_13 = j.powf(-1.0 / 3.0);
let f_bar: [[f64; 3]; 3] = {
let mut fb = [[0.0f64; 3]; 3];
for i in 0..3 {
for j in 0..3 {
fb[i][j] = j_13 * f[i][j];
}
}
fb
};
let mut fa = [0.0f64; 3];
for i in 0..3 {
for k in 0..3 {
fa[i] += f_bar[i][k] * a[k];
}
}
fa[0] * fa[0] + fa[1] * fa[1] + fa[2] * fa[2]
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let j = det3(f);
let (i1, _i2, i3) = deformation_invariants(f);
let j_23 = i3.sqrt().powf(-2.0 / 3.0);
let i1_bar = j_23 * i1;
let i4_bar_1 = Self::isochoric_fiber_stretch_sq(f, &self.a1);
let i4_bar_2 = Self::isochoric_fiber_stretch_sq(f, &self.a2);
let e1 = Self::generalized_invariant(i1_bar, i4_bar_1, self.kappa_disp) - 1.0;
let e2 = Self::generalized_invariant(i1_bar, i4_bar_2, self.kappa_disp) - 1.0;
let w_matrix = self.c1 / 2.0 * (i1_bar - 3.0);
let w_f1 = if e1 > 0.0 {
self.k1 / (2.0 * self.k2) * ((self.k2 * e1 * e1).exp() - 1.0)
} else {
0.0
};
let w_f2 = if e2 > 0.0 {
self.k1 / (2.0 * self.k2) * ((self.k2 * e2 * e2).exp() - 1.0)
} else {
0.0
};
let w_vol = self.bulk_modulus / 2.0 * (j - 1.0).powi(2);
w_matrix + w_f1 + w_f2 + w_vol
}
pub fn fibers_active(&self, f: &[[f64; 3]; 3]) -> (bool, bool) {
let (i1, _i2, i3) = deformation_invariants(f);
let j_23 = i3.sqrt().powf(-2.0 / 3.0);
let i1_bar = j_23 * i1;
let i4_bar_1 = Self::isochoric_fiber_stretch_sq(f, &self.a1);
let i4_bar_2 = Self::isochoric_fiber_stretch_sq(f, &self.a2);
let e1 = Self::generalized_invariant(i1_bar, i4_bar_1, self.kappa_disp) - 1.0;
let e2 = Self::generalized_invariant(i1_bar, i4_bar_2, self.kappa_disp) - 1.0;
(e1 > 0.0, e2 > 0.0)
}
}
#[derive(Debug, Clone)]
pub struct Gent {
pub mu: f64,
pub j_m: f64,
pub bulk_modulus: f64,
}
impl Gent {
pub fn new(mu: f64, j_m: f64, bulk_modulus: f64) -> Self {
Self {
mu,
j_m,
bulk_modulus,
}
}
pub fn strain_energy_from_stretches(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> f64 {
let j = lambda1 * lambda2 * lambda3;
let j13 = j.powf(-1.0 / 3.0);
let l1 = j13 * lambda1;
let l2 = j13 * lambda2;
let l3 = j13 * lambda3;
let i1_bar = l1 * l1 + l2 * l2 + l3 * l3;
let arg = 1.0 - (i1_bar - 3.0) / self.j_m;
if arg <= 0.0 {
return f64::INFINITY;
}
let d1 = self.bulk_modulus / 2.0;
-self.mu * self.j_m / 2.0 * arg.ln() + d1 * (j - 1.0).powi(2)
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let (l1, l2, l3) = principal_stretches(f);
self.strain_energy_from_stretches(l1, l2, l3)
}
pub fn shear_modulus(&self) -> f64 {
self.mu
}
pub fn bulk_modulus(&self) -> f64 {
self.bulk_modulus
}
}
#[derive(Debug, Clone)]
pub struct DruckerPrager {
pub friction_angle: f64,
pub cohesion: f64,
pub young_modulus: f64,
pub poisson_ratio: f64,
}
impl DruckerPrager {
pub fn new(friction_angle: f64, cohesion: f64, young_modulus: f64, poisson_ratio: f64) -> Self {
Self {
friction_angle,
cohesion,
young_modulus,
poisson_ratio,
}
}
pub fn alpha(&self) -> f64 {
2.0 * self.friction_angle.sin() / (3.0_f64.sqrt() * (3.0 - self.friction_angle.sin()))
}
pub fn k(&self) -> f64 {
6.0 * self.cohesion * self.friction_angle.cos()
/ (3.0_f64.sqrt() * (3.0 - self.friction_angle.sin()))
}
pub fn yield_function(&self, stress: &[f64; 6]) -> f64 {
let i1 = stress[0] + stress[1] + stress[2];
let j2 = Self::j2_invariant(stress);
j2.sqrt() + self.alpha() * i1 - self.k()
}
fn j2_invariant(stress: &[f64; 6]) -> f64 {
let mean = (stress[0] + stress[1] + stress[2]) / 3.0;
let dev = [stress[0] - mean, stress[1] - mean, stress[2] - mean];
0.5 * (dev[0].powi(2) + dev[1].powi(2) + dev[2].powi(2))
+ stress[3].powi(2)
+ stress[4].powi(2)
+ stress[5].powi(2)
}
}
#[derive(Debug, Clone)]
pub struct JwlEos {
pub a: f64,
pub b: f64,
pub r1: f64,
pub r2: f64,
pub omega: f64,
pub rho0: f64,
}
impl JwlEos {
pub fn new(a: f64, b: f64, r1: f64, r2: f64, omega: f64, rho0: f64) -> Self {
Self {
a,
b,
r1,
r2,
omega,
rho0,
}
}
pub fn pressure(&self, density: f64, energy: f64) -> f64 {
let v = self.rho0 / density;
let p1 = self.a * (1.0 - self.omega / (self.r1 * v)) * (-self.r1 * v).exp();
let p2 = self.b * (1.0 - self.omega / (self.r2 * v)) * (-self.r2 * v).exp();
let p3 = self.omega * energy * density;
p1 + p2 + p3
}
}
#[derive(Debug, Clone)]
pub struct Yeoh {
pub c10: f64,
pub c20: f64,
pub c30: f64,
pub bulk_modulus: f64,
}
impl Yeoh {
pub fn new(c10: f64, c20: f64, c30: f64, bulk_modulus: f64) -> Self {
Self {
c10,
c20,
c30,
bulk_modulus,
}
}
pub fn shear_modulus(&self) -> f64 {
2.0 * self.c10
}
pub fn strain_energy_from_stretches(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> f64 {
let j = lambda1 * lambda2 * lambda3;
let j13 = j.powf(-1.0 / 3.0);
let l1 = j13 * lambda1;
let l2 = j13 * lambda2;
let l3 = j13 * lambda3;
let i1_bar = l1 * l1 + l2 * l2 + l3 * l3;
let di = i1_bar - 3.0;
let d1 = self.bulk_modulus / 2.0;
self.c10 * di + self.c20 * di.powi(2) + self.c30 * di.powi(3) + d1 * (j - 1.0).powi(2)
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let (l1, l2, l3) = principal_stretches(f);
self.strain_energy_from_stretches(l1, l2, l3)
}
}
#[derive(Debug, Clone)]
pub struct MooneyRivlin {
pub c10: f64,
pub c01: f64,
pub bulk_modulus: f64,
}
impl MooneyRivlin {
pub fn new(c10: f64, c01: f64, bulk_modulus: f64) -> Self {
Self {
c10,
c01,
bulk_modulus,
}
}
pub fn from_shear_and_ratio(shear_modulus: f64, ratio: f64, bulk_modulus: f64) -> Self {
let c01 = shear_modulus / (2.0 * (1.0 + ratio));
let c10 = ratio * c01;
Self::new(c10, c01, bulk_modulus)
}
pub fn shear_modulus(&self) -> f64 {
2.0 * (self.c10 + self.c01)
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let (i1, i2, i3) = deformation_invariants(f);
let j = i3.sqrt();
let j_23 = j.powf(-2.0 / 3.0);
let i1_bar = j_23 * i1;
let i2_bar = j_23 * j_23 * i2;
let d1 = self.bulk_modulus / 2.0;
self.c10 * (i1_bar - 3.0) + self.c01 * (i2_bar - 3.0) + d1 * (j - 1.0).powi(2)
}
#[allow(clippy::needless_range_loop)]
pub fn first_piola_kirchhoff_stress(&self, f: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
let j = det3(f);
if j.abs() < 1e-30 {
return [[0.0; 3]; 3];
}
let j_23 = j.powf(-2.0 / 3.0);
let f_inv_t = transpose3(&inv3(f));
let mut c = [[0.0; 3]; 3];
for i in 0..3 {
for k in 0..3 {
for l in 0..3 {
c[i][k] += f[l][i] * f[l][k];
}
}
}
let i1 = c[0][0] + c[1][1] + c[2][2];
let mut p = [[0.0; 3]; 3];
for i in 0..3 {
for j_idx in 0..3 {
let iso1 = 2.0 * self.c10 * j_23 * (f[i][j_idx] - i1 / 3.0 * f_inv_t[i][j_idx]);
let iso2 = 2.0
* self.c01
* j_23
* j_23
* (i1 * f[i][j_idx]
- {
let mut cfij = 0.0;
for k in 0..3 {
cfij += c[j_idx][k] * f[i][k];
}
cfij
}
- (i1 * i1 - {
let mut c2_trace = 0.0;
for a in 0..3 {
for b in 0..3 {
c2_trace += c[a][b] * c[b][a];
}
}
c2_trace
}) / 3.0
* f_inv_t[i][j_idx]);
let vol = self.bulk_modulus * (j - 1.0) * j * f_inv_t[i][j_idx];
p[i][j_idx] = iso1 + iso2 + vol;
}
}
p
}
}
#[derive(Debug, Clone)]
pub struct BilinearCohesiveZone {
pub stiffness: f64,
pub delta0: f64,
pub delta_f: f64,
}
impl BilinearCohesiveZone {
pub fn new(stiffness: f64, delta0: f64, delta_f: f64) -> Self {
assert!(delta_f > delta0, "delta_f must exceed delta0");
Self {
stiffness,
delta0,
delta_f,
}
}
pub fn peak_traction(&self) -> f64 {
self.stiffness * self.delta0
}
pub fn fracture_toughness(&self) -> f64 {
self.peak_traction() * self.delta_f / 2.0
}
pub fn traction(&self, delta: f64) -> f64 {
if delta <= 0.0 {
return 0.0;
}
if delta <= self.delta0 {
self.stiffness * delta
} else if delta < self.delta_f {
self.peak_traction() * (self.delta_f - delta) / (self.delta_f - self.delta0)
} else {
0.0
}
}
pub fn damage_variable(&self, delta_max: f64) -> f64 {
if delta_max <= self.delta0 {
0.0
} else if delta_max >= self.delta_f {
1.0
} else {
self.delta_f * (delta_max - self.delta0) / (delta_max * (self.delta_f - self.delta0))
}
}
pub fn dissipated_energy(&self, delta: f64) -> f64 {
if delta <= 0.0 {
0.0
} else if delta <= self.delta0 {
0.5 * self.stiffness * delta * delta
} else if delta < self.delta_f {
let t_max = self.peak_traction();
let e0 = 0.5 * t_max * self.delta0;
let slope = t_max / (self.delta_f - self.delta0);
let dd = delta - self.delta0;
let ddf = self.delta_f - self.delta0;
e0 + t_max * dd - 0.5 * slope * dd * dd - (t_max * ddf - 0.5 * slope * ddf * ddf) * 0.0
} else {
self.fracture_toughness()
}
}
}
#[derive(Debug, Clone)]
pub struct J2Plasticity {
pub young_modulus: f64,
pub poisson_ratio: f64,
pub yield_stress: f64,
pub hardening_modulus: f64,
}
impl J2Plasticity {
pub fn new(
young_modulus: f64,
poisson_ratio: f64,
yield_stress: f64,
hardening_modulus: f64,
) -> Self {
Self {
young_modulus,
poisson_ratio,
yield_stress,
hardening_modulus,
}
}
pub fn shear_modulus(&self) -> f64 {
self.young_modulus / (2.0 * (1.0 + self.poisson_ratio))
}
pub fn bulk_modulus(&self) -> f64 {
self.young_modulus / (3.0 * (1.0 - 2.0 * self.poisson_ratio))
}
pub fn current_yield_stress(&self, equivalent_plastic_strain: f64) -> f64 {
self.yield_stress + self.hardening_modulus * equivalent_plastic_strain
}
pub fn von_mises_stress(stress: &[f64; 6]) -> f64 {
let s_xx = stress[0];
let s_yy = stress[1];
let s_zz = stress[2];
let s_xy = stress[3];
let s_yz = stress[4];
let s_xz = stress[5];
let term1 = (s_xx - s_yy).powi(2) + (s_yy - s_zz).powi(2) + (s_zz - s_xx).powi(2);
let term2 = 6.0 * (s_xy.powi(2) + s_yz.powi(2) + s_xz.powi(2));
((term1 + term2) / 2.0).sqrt()
}
pub fn return_mapping(
&self,
trial_stress: &[f64; 6],
equivalent_plastic_strain: f64,
) -> ([f64; 6], f64) {
let vm = Self::von_mises_stress(trial_stress);
let sigma_y = self.current_yield_stress(equivalent_plastic_strain);
if vm <= sigma_y {
return (*trial_stress, 0.0);
}
let g = self.shear_modulus();
let delta_gamma = (vm - sigma_y) / (3.0 * g + self.hardening_modulus);
let mean = (trial_stress[0] + trial_stress[1] + trial_stress[2]) / 3.0;
let dev = [
trial_stress[0] - mean,
trial_stress[1] - mean,
trial_stress[2] - mean,
trial_stress[3],
trial_stress[4],
trial_stress[5],
];
let scale = 1.0 - 3.0 * g * delta_gamma / vm;
let stress = [
mean + dev[0] * scale,
mean + dev[1] * scale,
mean + dev[2] * scale,
dev[3] * scale,
dev[4] * scale,
dev[5] * scale,
];
(stress, delta_gamma)
}
}
impl J2Plasticity {
pub fn consistent_tangent_uniaxial(&self) -> f64 {
self.young_modulus * self.hardening_modulus / (self.young_modulus + self.hardening_modulus)
}
pub fn flow_stress(&self, ep_bar: f64) -> f64 {
self.current_yield_stress(ep_bar)
}
pub fn back_stress_increment(&self, c_kinematic: f64, delta_ep_voigt: &[f64; 6]) -> [f64; 6] {
let scale = 2.0 / 3.0 * c_kinematic;
[
scale * delta_ep_voigt[0],
scale * delta_ep_voigt[1],
scale * delta_ep_voigt[2],
scale * delta_ep_voigt[3],
scale * delta_ep_voigt[4],
scale * delta_ep_voigt[5],
]
}
pub fn equivalent_plastic_strain_increment(delta_ep_voigt: &[f64; 6]) -> f64 {
let sum = delta_ep_voigt[0].powi(2)
+ delta_ep_voigt[1].powi(2)
+ delta_ep_voigt[2].powi(2)
+ 2.0
* (delta_ep_voigt[3].powi(2)
+ delta_ep_voigt[4].powi(2)
+ delta_ep_voigt[5].powi(2));
(2.0 / 3.0 * sum).sqrt()
}
pub fn return_mapping_plane_stress(
&self,
trial_sxx: f64,
trial_syy: f64,
trial_sxy: f64,
ep_bar: f64,
) -> ([f64; 3], f64) {
let trial_stress_full = [trial_sxx, trial_syy, 0.0, trial_sxy, 0.0, 0.0];
let vm = Self::von_mises_stress(&trial_stress_full);
let sigma_y = self.current_yield_stress(ep_bar);
if vm <= sigma_y {
return ([trial_sxx, trial_syy, trial_sxy], 0.0);
}
let g = self.shear_modulus();
let delta_gamma = (vm - sigma_y) / (3.0 * g + self.hardening_modulus);
let scale = 1.0 - 3.0 * g * delta_gamma / vm;
let mean = (trial_sxx + trial_syy) / 3.0;
let dev_xx = trial_sxx - mean;
let dev_yy = trial_syy - mean;
(
[
mean + dev_xx * scale,
mean + dev_yy * scale,
trial_sxy * scale,
],
delta_gamma,
)
}
}
#[derive(Debug, Clone)]
pub struct ArrudaBoyce {
pub mu: f64,
pub lambda_m: f64,
pub bulk_modulus: f64,
}
impl ArrudaBoyce {
const C: [f64; 5] = [
0.5,
1.0 / 20.0,
11.0 / 1050.0,
19.0 / 7000.0,
519.0 / 673750.0,
];
pub fn new(mu: f64, lambda_m: f64, bulk_modulus: f64) -> Self {
Self {
mu,
lambda_m,
bulk_modulus,
}
}
pub fn strain_energy_from_stretches(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> f64 {
let j = lambda1 * lambda2 * lambda3;
let j13 = j.powf(-1.0 / 3.0);
let l1 = j13 * lambda1;
let l2 = j13 * lambda2;
let l3 = j13 * lambda3;
let i1_bar = l1 * l1 + l2 * l2 + l3 * l3;
let mut w = 0.0;
let lm2 = self.lambda_m * self.lambda_m;
let mut lm_pow = 1.0_f64;
for i in 0..5 {
let i1_pow_i = i1_bar.powi((i + 1) as i32);
let three_pow_i = 3.0_f64.powi((i + 1) as i32);
w += self.mu * Self::C[i] / lm_pow * (i1_pow_i - three_pow_i);
lm_pow *= lm2;
}
let d1 = self.bulk_modulus / 2.0;
w + d1 * (j - 1.0).powi(2)
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let (l1, l2, l3) = principal_stretches(f);
self.strain_energy_from_stretches(l1, l2, l3)
}
pub fn shear_modulus(&self) -> f64 {
self.mu
}
}
#[derive(Debug, Clone)]
pub struct MieGruneisenEos {
pub rho0: f64,
pub c0: f64,
pub s: f64,
pub gamma0: f64,
}
impl MieGruneisenEos {
pub fn new(rho0: f64, c0: f64, s: f64, gamma0: f64) -> Self {
Self {
rho0,
c0,
s,
gamma0,
}
}
pub fn pressure(&self, density: f64, energy: f64) -> f64 {
let eta = 1.0 - self.rho0 / density;
if eta.abs() < 1e-12 {
return self.gamma0 * density * energy;
}
let denom = 1.0 - self.s * eta;
if denom.abs() < 1e-12 {
return self.gamma0 * density * energy;
}
let p_h = self.rho0 * self.c0 * self.c0 * eta / (denom * denom);
let e_h = 0.5 * p_h * eta / self.rho0;
p_h + self.gamma0 * density * (energy - e_h)
}
pub fn sound_speed_reference(&self) -> f64 {
self.c0
}
}
#[derive(Debug, Clone)]
pub struct Fung {
pub c: f64,
pub b1: f64,
pub b2: f64,
pub bulk_modulus: f64,
}
impl Fung {
pub fn new(c: f64, b1: f64, b2: f64, bulk_modulus: f64) -> Self {
Self {
c,
b1,
b2,
bulk_modulus,
}
}
pub fn aorta() -> Self {
Self::new(26.95, 1.95, 0.0, 1.0e6)
}
pub fn myocardium() -> Self {
Self::new(100.0, 4.0, 0.5, 1.0e6)
}
pub fn strain_energy_from_stretches(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> f64 {
let j = lambda1 * lambda2 * lambda3;
let j13 = j.powf(-1.0 / 3.0);
let l1 = j13 * lambda1;
let l2 = j13 * lambda2;
let l3 = j13 * lambda3;
let i1_bar = l1 * l1 + l2 * l2 + l3 * l3;
let di = i1_bar - 3.0;
let q = self.b1 * di + self.b2 * di * di;
let d1 = self.bulk_modulus / 2.0;
self.c / 2.0 * (q.exp() - 1.0) + d1 * (j - 1.0).powi(2)
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let (l1, l2, l3) = principal_stretches(f);
self.strain_energy_from_stretches(l1, l2, l3)
}
pub fn uniaxial_cauchy_stress(&self, lambda: f64) -> f64 {
let inv = 1.0 / lambda.sqrt();
let h = 1e-6;
let w_plus = self.strain_energy_from_stretches(lambda + h, inv, inv);
let w_minus = self.strain_energy_from_stretches(lambda - h, inv, inv);
lambda * (w_plus - w_minus) / (2.0 * h)
}
pub fn small_strain_shear_modulus(&self) -> f64 {
self.c * self.b1
}
}
#[derive(Debug, Clone)]
pub struct Hencky {
pub lame_lambda: f64,
pub shear_modulus: f64,
}
impl Hencky {
pub fn new(lame_lambda: f64, shear_modulus: f64) -> Self {
Self {
lame_lambda,
shear_modulus,
}
}
#[allow(non_snake_case)]
pub fn from_young_poisson(E: f64, nu: f64) -> Self {
let lame_lambda = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu));
let mu = E / (2.0 * (1.0 + nu));
Self::new(lame_lambda, mu)
}
pub fn bulk_modulus(&self) -> f64 {
self.lame_lambda + 2.0 * self.shear_modulus / 3.0
}
pub fn young_modulus(&self) -> f64 {
let l = self.lame_lambda;
let m = self.shear_modulus;
m * (3.0 * l + 2.0 * m) / (l + m)
}
pub fn poisson_ratio(&self) -> f64 {
self.lame_lambda / (2.0 * (self.lame_lambda + self.shear_modulus))
}
pub fn strain_energy_from_stretches(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> f64 {
let h1 = lambda1.ln();
let h2 = lambda2.ln();
let h3 = lambda3.ln();
let h_sum = h1 + h2 + h3;
let h_sq = h1 * h1 + h2 * h2 + h3 * h3;
self.lame_lambda / 2.0 * h_sum * h_sum + self.shear_modulus * h_sq
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let (l1, l2, l3) = principal_stretches(f);
self.strain_energy_from_stretches(l1, l2, l3)
}
pub fn principal_cauchy_stress(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> [f64; 3] {
let h1 = lambda1.ln();
let h2 = lambda2.ln();
let h3 = lambda3.ln();
let j = lambda1 * lambda2 * lambda3;
let ln_j = j.ln();
let scale = 1.0 / j;
[
scale * (self.lame_lambda * ln_j + 2.0 * self.shear_modulus * h1),
scale * (self.lame_lambda * ln_j + 2.0 * self.shear_modulus * h2),
scale * (self.lame_lambda * ln_j + 2.0 * self.shear_modulus * h3),
]
}
}
#[derive(Debug, Clone)]
pub struct BlatzKo {
pub shear_modulus: f64,
pub f: f64,
pub poisson_ratio: f64,
}
impl BlatzKo {
pub fn new(shear_modulus: f64, f: f64, poisson_ratio: f64) -> Self {
Self {
shear_modulus,
f,
poisson_ratio,
}
}
pub fn foam_default(shear_modulus: f64) -> Self {
Self::new(shear_modulus, 0.5, 0.25)
}
pub fn strain_energy_from_stretches(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> f64 {
let j = lambda1 * lambda2 * lambda3;
let i1 = lambda1 * lambda1 + lambda2 * lambda2 + lambda3 * lambda3;
let i3 = j * j;
let w_isochoric = self.f * (i1 / i3.cbrt() - 3.0);
let w_volumetric =
(1.0 - self.f) * (1.0 / j.powf(2.0 * self.f / (1.0 - 2.0 * self.poisson_ratio)) - 1.0);
self.shear_modulus / 2.0 * (w_isochoric + w_volumetric)
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let (l1, l2, l3) = principal_stretches(f);
self.strain_energy_from_stretches(l1, l2, l3)
}
pub fn young_modulus(&self) -> f64 {
2.0 * self.shear_modulus * (1.0 + self.poisson_ratio)
}
}
#[derive(Debug, Clone)]
pub struct Varga {
pub shear_modulus: f64,
pub bulk_modulus: f64,
}
impl Varga {
pub fn new(shear_modulus: f64, bulk_modulus: f64) -> Self {
Self {
shear_modulus,
bulk_modulus,
}
}
pub fn strain_energy_from_stretches(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> f64 {
let j = lambda1 * lambda2 * lambda3;
let isochoric = 2.0 * self.shear_modulus * (lambda1 + lambda2 + lambda3 - 3.0);
let volumetric = self.bulk_modulus / 2.0 * (j - 1.0).powi(2);
isochoric + volumetric
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let (l1, l2, l3) = principal_stretches(f);
self.strain_energy_from_stretches(l1, l2, l3)
}
pub fn uniaxial_stress_incompressible(&self, lambda: f64) -> f64 {
2.0 * self.shear_modulus * (1.0 - lambda.powf(-1.5))
}
pub fn shear_modulus(&self) -> f64 {
self.shear_modulus
}
}
#[derive(Debug, Clone)]
pub struct HolzapfelOgden {
pub a: f64,
pub b: f64,
pub a_f1: f64,
pub b_f1: f64,
pub a_f2: f64,
pub b_f2: f64,
pub a1: [f64; 3],
pub a2: [f64; 3],
pub bulk_modulus: f64,
}
impl HolzapfelOgden {
#[allow(clippy::too_many_arguments)]
pub fn new(
a: f64,
b: f64,
a_f1: f64,
b_f1: f64,
a_f2: f64,
b_f2: f64,
a1: [f64; 3],
a2: [f64; 3],
bulk_modulus: f64,
) -> Self {
Self {
a,
b,
a_f1,
b_f1,
a_f2,
b_f2,
a1,
a2,
bulk_modulus,
}
}
pub fn single_family(a: f64, b: f64, a_f: f64, b_f: f64, fibre: [f64; 3], bulk: f64) -> Self {
Self::new(a, b, a_f, b_f, 0.0, 1.0, fibre, fibre, bulk)
}
pub fn myocardium_default() -> Self {
Self::new(
59.0,
8.023,
18472.0,
16.026,
2481.0,
11.12,
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
1.0e6,
)
}
#[allow(clippy::needless_range_loop)]
fn fibre_stretch_squared(f: &[[f64; 3]; 3], a: &[f64; 3]) -> f64 {
let mut fa = [0.0; 3];
for i in 0..3 {
for j in 0..3 {
fa[i] += f[i][j] * a[j];
}
}
fa[0] * fa[0] + fa[1] * fa[1] + fa[2] * fa[2]
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let j = det3(f);
let (i1, _i2, _i3) = deformation_invariants(f);
let w_iso = self.a / (2.0 * self.b) * ((self.b * (i1 - 3.0)).exp() - 1.0) - self.a * j.ln();
let i4_1 = Self::fibre_stretch_squared(f, &self.a1);
let w_f1 = if i4_1 > 1.0 {
self.a_f1 / (2.0 * self.b_f1) * ((self.b_f1 * (i4_1 - 1.0).powi(2)).exp() - 1.0)
} else {
0.0
};
let i4_2 = Self::fibre_stretch_squared(f, &self.a2);
let w_f2 = if i4_2 > 1.0 {
self.a_f2 / (2.0 * self.b_f2) * ((self.b_f2 * (i4_2 - 1.0).powi(2)).exp() - 1.0)
} else {
0.0
};
let w_vol = self.bulk_modulus / 2.0 * (j - 1.0).powi(2);
w_iso + w_f1 + w_f2 + w_vol
}
pub fn fibre_active(&self, f: &[[f64; 3]; 3], family: usize) -> bool {
let a = if family == 1 { &self.a1 } else { &self.a2 };
Self::fibre_stretch_squared(f, a) > 1.0
}
}
#[derive(Debug, Clone)]
pub struct Ogden {
pub mu: Vec<f64>,
pub alpha: Vec<f64>,
pub bulk_modulus: f64,
}
impl Ogden {
pub fn new(mu: Vec<f64>, alpha: Vec<f64>, bulk_modulus: f64) -> Self {
assert_eq!(mu.len(), alpha.len(), "mu and alpha must have same length");
Self {
mu,
alpha,
bulk_modulus,
}
}
pub fn one_term(mu: f64, alpha: f64, bulk_modulus: f64) -> Self {
Self::new(vec![mu], vec![alpha], bulk_modulus)
}
pub fn rubber_default() -> Self {
Self::new(vec![6.3e5, 1.2e3, -1.0e4], vec![1.3, 5.0, -2.0], 1.0e9)
}
pub fn shear_modulus(&self) -> f64 {
0.5 * self
.mu
.iter()
.zip(self.alpha.iter())
.map(|(m, a)| m * a)
.sum::<f64>()
}
pub fn strain_energy_from_stretches(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> f64 {
let j = lambda1 * lambda2 * lambda3;
let j_13 = j.powf(-1.0 / 3.0);
let l1 = j_13 * lambda1;
let l2 = j_13 * lambda2;
let l3 = j_13 * lambda3;
let mut w = 0.0;
for (mu_p, alpha_p) in self.mu.iter().zip(self.alpha.iter()) {
w += (mu_p / alpha_p)
* (l1.powf(*alpha_p) + l2.powf(*alpha_p) + l3.powf(*alpha_p) - 3.0);
}
let d1 = self.bulk_modulus / 2.0;
w += d1 * (j - 1.0).powi(2);
w
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
let (l1, l2, l3) = principal_stretches(f);
self.strain_energy_from_stretches(l1, l2, l3)
}
}