#[allow(unused_imports)]
use super::functions::*;
#[derive(Debug, Clone)]
pub struct CartilageBiphasic {
pub aggregate_modulus: f64,
pub permeability: f64,
pub poisson_ratio: f64,
pub solid_fraction: f64,
pub youngs_modulus: f64,
pub permeability_strain_coeff: f64,
pub(super) fluid_pressure: f64,
}
impl CartilageBiphasic {
pub fn new(
aggregate_modulus: f64,
permeability: f64,
poisson_ratio: f64,
solid_fraction: f64,
) -> Self {
let nu = poisson_ratio;
let ha = aggregate_modulus;
let youngs = ha * (1.0 + nu) * (1.0 - 2.0 * nu) / (1.0 - nu);
Self {
aggregate_modulus,
permeability,
poisson_ratio,
solid_fraction,
youngs_modulus: youngs,
permeability_strain_coeff: 5.0,
fluid_pressure: 0.0,
}
}
pub fn superficial_zone() -> Self {
Self::new(0.42e6, 4.0e-15, 0.1, 0.15)
}
pub fn middle_zone() -> Self {
Self::new(0.60e6, 2.0e-15, 0.25, 0.20)
}
pub fn deep_zone() -> Self {
Self::new(0.85e6, 1.0e-15, 0.35, 0.25)
}
pub fn current_permeability(&self, volumetric_strain: f64) -> f64 {
self.permeability * (self.permeability_strain_coeff * volumetric_strain).exp()
}
pub fn effective_stress(&self, strain: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
let e_mod = self.youngs_modulus;
let nu = self.poisson_ratio;
let lambda = e_mod * nu / ((1.0 + nu) * (1.0 - 2.0 * nu));
let mu = e_mod / (2.0 * (1.0 + nu));
let tr_e = trace3(strain);
let mut sigma = [[0.0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
sigma[i][j] = 2.0 * mu * strain[i][j];
if i == j {
sigma[i][j] += lambda * tr_e;
}
}
}
sigma
}
#[allow(clippy::needless_range_loop)]
pub fn total_stress(&self, strain: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
let mut sigma = self.effective_stress(strain);
for i in 0..3 {
sigma[i][i] -= self.fluid_pressure;
}
sigma
}
pub fn update_pressure(
&mut self,
volumetric_strain_rate: f64,
characteristic_length: f64,
dt: f64,
) -> f64 {
let k = self.permeability;
let ha = self.aggregate_modulus;
let l2 = characteristic_length * characteristic_length;
let diffusivity = k * ha / l2;
let dp = (-ha * volumetric_strain_rate - diffusivity * self.fluid_pressure) * dt;
self.fluid_pressure += dp;
self.fluid_pressure
}
pub fn consolidation_time(&self, characteristic_length: f64) -> f64 {
let l2 = characteristic_length * characteristic_length;
l2 / (self.aggregate_modulus * self.permeability)
}
pub fn porosity(&self) -> f64 {
1.0 - self.solid_fraction
}
pub fn fluid_pressure(&self) -> f64 {
self.fluid_pressure
}
pub fn set_fluid_pressure(&mut self, p: f64) {
self.fluid_pressure = p;
}
}
#[derive(Debug, Clone)]
pub struct QuasiLinearViscoelastic {
pub elastic: FungHyperelastic,
pub g_coeffs: Vec<f64>,
pub tau_coeffs: Vec<f64>,
pub g_inf: f64,
pub(super) state: Vec<f64>,
pub(super) prev_stress: f64,
}
impl QuasiLinearViscoelastic {
pub fn new(
elastic: FungHyperelastic,
g_coeffs: Vec<f64>,
tau_coeffs: Vec<f64>,
g_inf: f64,
) -> Self {
let n = g_coeffs.len();
Self {
elastic,
g_coeffs,
tau_coeffs,
g_inf,
state: vec![0.0; n],
prev_stress: 0.0,
}
}
pub fn tendon_default() -> Self {
let elastic = FungHyperelastic::isotropic(200.0, 30.0);
Self::new(elastic, vec![0.3, 0.15, 0.05], vec![1.0, 10.0, 100.0], 0.5)
}
pub fn reset(&mut self) {
for s in &mut self.state {
*s = 0.0;
}
self.prev_stress = 0.0;
}
pub fn update(&mut self, f: &[[f64; 3]; 3], dt: f64) -> f64 {
let e = green_lagrange(f);
let q = self.elastic.compute_q(&e);
let elastic_stress = self.elastic.c * q.exp() * q;
let d_stress = elastic_stress - self.prev_stress;
for (i, state_i) in self.state.iter_mut().enumerate() {
let tau = self.tau_coeffs[i];
let exp_dt = (-dt / tau).exp();
*state_i = exp_dt * (*state_i) + self.g_coeffs[i] * d_stress;
}
let viscoelastic_stress = self.g_inf * elastic_stress + self.state.iter().sum::<f64>();
self.prev_stress = elastic_stress;
viscoelastic_stress
}
pub fn relaxation_function(&self, t: f64) -> f64 {
let mut g = self.g_inf;
for (i, &gi) in self.g_coeffs.iter().enumerate() {
g += gi * (-t / self.tau_coeffs[i]).exp();
}
g
}
pub fn instantaneous_modulus_ratio(&self) -> f64 {
self.g_inf + self.g_coeffs.iter().sum::<f64>()
}
}
#[derive(Debug, Clone)]
pub struct MultiLayerTissue {
pub layers: Vec<TissueLayer>,
}
impl MultiLayerTissue {
pub fn new(layers: Vec<TissueLayer>) -> Self {
Self { layers }
}
pub fn skin_fat_muscle() -> Self {
Self::new(vec![
TissueLayer::new(TissueLayerType::Epithelial, 0.002, 1100.0, 0.6e6, 0.49),
TissueLayer::new(TissueLayerType::Adipose, 0.010, 900.0, 0.003e6, 0.49),
TissueLayer::new(TissueLayerType::Muscular, 0.030, 1060.0, 0.5e6, 0.45),
])
}
pub fn total_thickness(&self) -> f64 {
self.layers.iter().map(|l| l.thickness).sum()
}
pub fn mass_per_area(&self) -> f64 {
self.layers.iter().map(|l| l.density * l.thickness).sum()
}
pub fn effective_youngs_modulus_voigt(&self) -> f64 {
let total_t = self.total_thickness();
if total_t < 1e-15 {
return 0.0;
}
self.layers
.iter()
.map(|l| l.youngs_modulus * l.thickness)
.sum::<f64>()
/ total_t
}
pub fn effective_youngs_modulus_reuss(&self) -> f64 {
let total_t = self.total_thickness();
if total_t < 1e-15 {
return 0.0;
}
let inv_sum: f64 = self
.layers
.iter()
.map(|l| {
if l.youngs_modulus > 1e-30 {
l.thickness / l.youngs_modulus
} else {
0.0
}
})
.sum();
if inv_sum > 1e-30 {
total_t / inv_sum
} else {
0.0
}
}
pub fn layer_depth(&self, layer_type: TissueLayerType) -> Option<f64> {
let mut depth = 0.0;
for l in &self.layers {
if l.layer_type == layer_type {
return Some(depth);
}
depth += l.thickness;
}
None
}
}
#[derive(Debug, Clone)]
pub struct BoneRemodeling {
pub density: f64,
pub density_min: f64,
pub density_max: f64,
pub remodeling_rate: f64,
pub stimulus_ref: f64,
pub dead_zone: f64,
pub density_exponent: f64,
pub e_ref: f64,
pub rho_ref: f64,
}
impl BoneRemodeling {
pub fn new(
density: f64,
density_min: f64,
density_max: f64,
remodeling_rate: f64,
stimulus_ref: f64,
dead_zone: f64,
) -> Self {
Self {
density,
density_min,
density_max,
remodeling_rate,
stimulus_ref,
dead_zone,
density_exponent: 2.0,
e_ref: 17.0e9,
rho_ref: 1900.0,
}
}
pub fn cortical_bone() -> Self {
Self::new(1900.0, 500.0, 2100.0, 0.1, 0.004, 0.1)
}
pub fn trabecular_bone() -> Self {
Self::new(800.0, 100.0, 1500.0, 0.15, 0.002, 0.15)
}
pub fn elastic_modulus(&self) -> f64 {
self.e_ref * (self.density / self.rho_ref).powf(self.density_exponent)
}
pub fn strain_energy_density(&self, strain: &[[f64; 3]; 3]) -> f64 {
let e_mod = self.elastic_modulus();
let nu = 0.3;
let lambda = e_mod * nu / ((1.0 + nu) * (1.0 - 2.0 * nu));
let mu = e_mod / (2.0 * (1.0 + nu));
let tr_e = trace3(strain);
let e2 = double_contract(strain, strain);
0.5 * lambda * tr_e * tr_e + mu * e2
}
pub fn update_density(&mut self, stimulus: f64, dt: f64) -> f64 {
let s_ratio = stimulus / self.stimulus_ref;
let deviation = s_ratio - 1.0;
if deviation.abs() > self.dead_zone {
let effective_deviation = if deviation > 0.0 {
deviation - self.dead_zone
} else {
deviation + self.dead_zone
};
let d_rho = self.remodeling_rate * effective_deviation * dt;
self.density = clamp(self.density + d_rho, self.density_min, self.density_max);
}
self.density
}
pub fn quality_index(&self) -> f64 {
(self.density - self.density_min) / (self.density_max - self.density_min)
}
pub fn is_osteoporotic(&self) -> bool {
self.density < 0.5 * self.rho_ref
}
pub fn yield_stress(&self) -> f64 {
let rho_gcc = self.density / 1000.0;
137.0e6 * rho_gcc.powf(1.88)
}
}
#[derive(Debug, Clone)]
pub struct HolzapfelGasserOgden {
pub mu: f64,
pub k1: f64,
pub k2: f64,
pub kappa: f64,
pub a1: [f64; 3],
pub a2: [f64; 3],
pub bulk_modulus: f64,
}
impl HolzapfelGasserOgden {
pub fn new(
mu: f64,
k1: f64,
k2: f64,
kappa: f64,
a1: [f64; 3],
a2: [f64; 3],
bulk_modulus: f64,
) -> Self {
let n1 = norm3(&a1);
let n2 = norm3(&a2);
let a1n = if n1 > 1e-15 {
scale3(&a1, 1.0 / n1)
} else {
a1
};
let a2n = if n2 > 1e-15 {
scale3(&a2, 1.0 / n2)
} else {
a2
};
Self {
mu,
k1,
k2,
kappa: kappa.clamp(0.0, 1.0 / 3.0),
a1: a1n,
a2: a2n,
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],
100.0e3,
)
}
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],
100.0e3,
)
}
pub fn arterial_wall() -> Self {
let angle = 39.0_f64.to_radians();
Self::new(
15.0e3,
2.3632e3,
0.8393,
0.226,
[angle.cos(), angle.sin(), 0.0],
[angle.cos(), -angle.sin(), 0.0],
100.0e3,
)
}
fn compute_i4(&self, c: &[[f64; 3]; 3], a: &[f64; 3]) -> f64 {
let mut i4 = 0.0;
for i in 0..3 {
for j in 0..3 {
i4 += a[i] * c[i][j] * a[j];
}
}
i4
}
pub fn strain_energy_density(&self, f: &[[f64; 3]; 3]) -> f64 {
self.strain_energy(f)
}
fn fiber_strain(&self, i1: f64, i4: f64) -> f64 {
let e = self.kappa * (i1 - 3.0) + (1.0 - 3.0 * self.kappa) * (i4 - 1.0);
e.max(0.0)
}
pub fn strain_energy(&self, f: &[[f64; 3]; 3]) -> f64 {
let c = right_cauchy_green(f);
let j = jacobian(f);
let i1 = invariant_i1(&c);
let i4_1 = self.compute_i4(&c, &self.a1);
let i4_2 = self.compute_i4(&c, &self.a2);
let j_23 = j.powf(-2.0 / 3.0);
let i1_bar = j_23 * i1;
let w_iso = 0.5 * self.mu * (i1_bar - 3.0);
let e1 = self.fiber_strain(i1_bar, i4_1);
let e2 = self.fiber_strain(i1_bar, i4_2);
let w_fib = if self.k2.abs() > 1e-30 {
(self.k1 / (2.0 * self.k2))
* ((self.k2 * e1 * e1).exp() - 1.0 + (self.k2 * e2 * e2).exp() - 1.0)
} else {
self.k1 * (e1 * e1 + e2 * e2)
};
let w_vol = 0.5 * self.bulk_modulus * (j - 1.0) * (j - 1.0);
w_iso + w_fib + w_vol
}
pub fn isotropic_cauchy_stress(&self, f: &[[f64; 3]; 3]) -> f64 {
let c = right_cauchy_green(f);
let j = jacobian(f);
let j_23 = j.powf(-2.0 / 3.0);
let _i1_bar = j_23 * invariant_i1(&c);
self.mu * j_23 / j
}
pub fn fiber_stress(&self, f: &[[f64; 3]; 3], fiber_index: usize) -> f64 {
let c = right_cauchy_green(f);
let j = jacobian(f);
let j_23 = j.powf(-2.0 / 3.0);
let i1_bar = j_23 * invariant_i1(&c);
let a = if fiber_index == 0 { &self.a1 } else { &self.a2 };
let i4 = self.compute_i4(&c, a);
let e = self.fiber_strain(i1_bar, i4);
if e > 0.0 {
2.0 * self.k1 * e * (self.k2 * e * e).exp()
} else {
0.0
}
}
pub fn volumetric_stress(&self, f: &[[f64; 3]; 3]) -> f64 {
let j = jacobian(f);
self.bulk_modulus * (j - 1.0)
}
}
#[derive(Debug, Clone)]
pub struct TissueLayer {
pub layer_type: TissueLayerType,
pub thickness: f64,
pub density: f64,
pub youngs_modulus: f64,
pub poisson_ratio: f64,
}
impl TissueLayer {
pub fn new(
layer_type: TissueLayerType,
thickness: f64,
density: f64,
youngs_modulus: f64,
poisson_ratio: f64,
) -> Self {
Self {
layer_type,
thickness,
density,
youngs_modulus,
poisson_ratio,
}
}
pub fn shear_modulus(&self) -> f64 {
self.youngs_modulus / (2.0 * (1.0 + self.poisson_ratio))
}
pub fn bulk_modulus(&self) -> f64 {
self.youngs_modulus / (3.0 * (1.0 - 2.0 * self.poisson_ratio))
}
pub fn constrained_modulus(&self) -> f64 {
let nu = self.poisson_ratio;
self.youngs_modulus * (1.0 - nu) / ((1.0 + nu) * (1.0 - 2.0 * nu))
}
}
#[derive(Debug, Clone)]
pub struct TissueDamage {
pub damage: f64,
pub threshold_strain: f64,
pub damage_rate: f64,
pub(super) max_strain: f64,
}
impl TissueDamage {
pub fn new(threshold_strain: f64, damage_rate: f64) -> Self {
Self {
damage: 0.0,
threshold_strain,
damage_rate,
max_strain: 0.0,
}
}
pub fn soft_tissue_default() -> Self {
Self::new(0.3, 2.0)
}
pub fn update(&mut self, effective_strain: f64) -> f64 {
if effective_strain > self.max_strain {
self.max_strain = effective_strain;
}
if self.max_strain > self.threshold_strain {
let excess = self.max_strain - self.threshold_strain;
self.damage = 1.0 - (-self.damage_rate * excess).exp();
}
self.damage
}
pub fn effective_stress(&self, undamaged_stress: f64) -> f64 {
(1.0 - self.damage) * undamaged_stress
}
pub fn is_ruptured(&self) -> bool {
self.damage > 0.99
}
pub fn reset(&mut self) {
self.damage = 0.0;
self.max_strain = 0.0;
}
}
#[derive(Debug, Clone)]
pub struct SkinMechanicsOgden {
pub mu: Vec<f64>,
pub alpha: Vec<f64>,
pub bulk_modulus: f64,
pub langer_direction: [f64; 3],
pub anisotropy_ratio: f64,
pub pre_stress: f64,
}
impl SkinMechanicsOgden {
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,
langer_direction: [1.0, 0.0, 0.0],
anisotropy_ratio: 1.0,
pre_stress: 0.0,
}
}
pub fn human_skin() -> Self {
Self {
mu: vec![0.12e6, -0.05e6],
alpha: vec![10.0, -2.0],
bulk_modulus: 20.0e6,
langer_direction: [1.0, 0.0, 0.0],
anisotropy_ratio: 1.3,
pre_stress: 1.0e3,
}
}
pub fn aged_skin() -> Self {
Self {
mu: vec![0.08e6, -0.03e6],
alpha: vec![8.0, -1.5],
bulk_modulus: 15.0e6,
langer_direction: [1.0, 0.0, 0.0],
anisotropy_ratio: 1.1,
pre_stress: 0.5e3,
}
}
pub fn strain_energy_from_stretches(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> f64 {
let j = lambda1 * lambda2 * lambda3;
let j_inv_third = j.powf(-1.0 / 3.0);
let l1 = lambda1 * j_inv_third;
let l2 = lambda2 * j_inv_third;
let l3 = lambda3 * j_inv_third;
let mut w_dev = 0.0;
for p in 0..self.mu.len() {
let ap = self.alpha[p];
let mp = self.mu[p];
w_dev += (mp / ap) * (l1.powf(ap) + l2.powf(ap) + l3.powf(ap) - 3.0);
}
let w_vol = 0.5 * self.bulk_modulus * (j - 1.0) * (j - 1.0);
w_dev + w_vol
}
pub fn strain_energy(&self, f: &[[f64; 3]; 3]) -> f64 {
let stretches = self.principal_stretches(f);
self.strain_energy_from_stretches(stretches[0], stretches[1], stretches[2])
}
pub fn principal_cauchy_stress(&self, lambda1: f64, lambda2: f64, lambda3: f64) -> [f64; 3] {
let j = lambda1 * lambda2 * lambda3;
let j_inv_third = j.powf(-1.0 / 3.0);
let l = [
lambda1 * j_inv_third,
lambda2 * j_inv_third,
lambda3 * j_inv_third,
];
let mut sigma_dev = [0.0; 3];
for p in 0..self.mu.len() {
let ap = self.alpha[p];
let mp = self.mu[p];
let mean_pow: f64 = (l[0].powf(ap) + l[1].powf(ap) + l[2].powf(ap)) / 3.0;
for i in 0..3 {
sigma_dev[i] += (mp / j) * (l[i].powf(ap) - mean_pow);
}
}
let p = self.bulk_modulus * (j - 1.0);
[
sigma_dev[0] + p + self.pre_stress,
sigma_dev[1] + p + self.pre_stress,
sigma_dev[2] + p,
]
}
pub fn principal_stretches(&self, f: &[[f64; 3]; 3]) -> [f64; 3] {
let c = right_cauchy_green(f);
let i1 = invariant_i1(&c);
let i2 = invariant_i2(&c);
let i3 = invariant_i3(&c);
let eigenvalues = solve_cubic_symmetric(i1, i2, i3);
[
eigenvalues[0].max(1e-30).sqrt(),
eigenvalues[1].max(1e-30).sqrt(),
eigenvalues[2].max(1e-30).sqrt(),
]
}
pub fn initial_shear_modulus(&self) -> f64 {
let mut mu_total = 0.0;
for p in 0..self.mu.len() {
mu_total += 0.5 * self.mu[p] * self.alpha[p];
}
mu_total
}
pub fn directional_tension(&self, f: &[[f64; 3]; 3], direction: &[f64; 3]) -> f64 {
let c = right_cauchy_green(f);
let mut lambda_sq = 0.0;
for i in 0..3 {
for j in 0..3 {
lambda_sq += direction[i] * c[i][j] * direction[j];
}
}
let lambda = lambda_sq.max(1e-30).sqrt();
let mut stress = 0.0;
for p in 0..self.mu.len() {
let ap = self.alpha[p];
stress += self.mu[p] * (lambda.powf(ap - 1.0) - lambda.powf(-ap / 2.0 - 1.0));
}
stress + self.pre_stress
}
}
#[derive(Debug, Clone)]
pub struct TissueGrowth {
pub growth_tensor: [[f64; 3]; 3],
pub growth_rate: f64,
pub max_growth: f64,
pub target_stress: f64,
}
impl TissueGrowth {
pub fn new(growth_rate: f64, max_growth: f64, target_stress: f64) -> Self {
Self {
growth_tensor: identity3(),
growth_rate,
max_growth,
target_stress,
}
}
pub fn arterial_growth() -> Self {
Self::new(0.01, 1.5, 100.0e3)
}
pub fn update_isotropic(&mut self, current_stress: f64, dt: f64) {
let stress_ratio = current_stress / self.target_stress;
let d_theta = self.growth_rate * (stress_ratio - 1.0) * dt;
let theta = self.growth_tensor[0][0];
let new_theta = clamp(theta + d_theta, 1.0, self.max_growth);
self.growth_tensor = identity3();
for i in 0..3 {
self.growth_tensor[i][i] = new_theta;
}
}
#[allow(clippy::needless_range_loop)]
pub fn update_anisotropic(&mut self, current_stress: f64, direction: &[f64; 3], dt: f64) {
let stress_ratio = current_stress / self.target_stress;
let d_theta = self.growth_rate * (stress_ratio - 1.0) * dt;
let nn = outer3(direction, direction);
for i in 0..3 {
for j in 0..3 {
let new_val = self.growth_tensor[i][j] + d_theta * nn[i][j];
self.growth_tensor[i][j] = if i == j {
clamp(new_val, 1.0, self.max_growth)
} else {
new_val
};
}
}
}
pub fn elastic_deformation(&self, f: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
let fg_inv = inv3(&self.growth_tensor);
mat_mul3(f, &fg_inv)
}
pub fn growth_volume_ratio(&self) -> f64 {
det3(&self.growth_tensor)
}
}
#[derive(Debug, Clone)]
pub struct MooneyRivlinBiological {
pub c10: f64,
pub c01: f64,
pub c11: f64,
pub bulk_modulus: f64,
}
impl MooneyRivlinBiological {
pub fn new(c10: f64, c01: f64, c11: f64, bulk_modulus: f64) -> Self {
Self {
c10,
c01,
c11,
bulk_modulus,
}
}
pub fn soft_tissue() -> Self {
Self::new(1.0e3, 0.5e3, 0.1e3, 50.0e3)
}
pub fn brain_tissue() -> Self {
Self::new(0.5e3, 0.25e3, 0.05e3, 30.0e3)
}
pub fn strain_energy(&self, f: &[[f64; 3]; 3]) -> f64 {
let c = right_cauchy_green(f);
let j = jacobian(f);
let j_23 = j.powf(-2.0 / 3.0);
let j_43 = j_23 * j_23;
let i1_bar = j_23 * invariant_i1(&c);
let i2_bar = j_43 * invariant_i2(&c);
let w_dev = self.c10 * (i1_bar - 3.0)
+ self.c01 * (i2_bar - 3.0)
+ self.c11 * (i1_bar - 3.0) * (i2_bar - 3.0);
let w_vol = 0.5 * self.bulk_modulus * (j - 1.0) * (j - 1.0);
w_dev + w_vol
}
pub fn shear_modulus(&self) -> f64 {
2.0 * (self.c10 + self.c01)
}
pub fn deviatoric_stress(&self, f: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
let c = right_cauchy_green(f);
let j = jacobian(f);
let j_23 = j.powf(-2.0 / 3.0);
let j_43 = j_23 * j_23;
let i1_bar = j_23 * invariant_i1(&c);
let i2_bar = j_43 * invariant_i2(&c);
let dw_di1 = self.c10 + self.c11 * (i2_bar - 3.0);
let dw_di2 = self.c01 + self.c11 * (i1_bar - 3.0);
let mut s = [[0.0; 3]; 3];
let id = identity3();
for i in 0..3 {
for j_idx in 0..3 {
s[i][j_idx] =
2.0 * (dw_di1 * id[i][j_idx] + dw_di2 * (i1_bar * id[i][j_idx] - c[i][j_idx]));
}
}
s
}
pub fn tangent_modulus_estimate(&self, f: &[[f64; 3]; 3]) -> f64 {
let c = right_cauchy_green(f);
let j = jacobian(f);
let j_23 = j.powf(-2.0 / 3.0);
let j_43 = j_23 * j_23;
let i1_bar = j_23 * invariant_i1(&c);
let i2_bar = j_43 * invariant_i2(&c);
4.0 * (self.c10 + self.c01 * i1_bar + self.c11 * (i1_bar + i2_bar - 6.0))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TissueLayerType {
Epithelial,
Connective,
Muscular,
Adipose,
Cartilage,
Bone,
Vascular,
Neural,
}
#[derive(Debug, Clone)]
pub struct HillMuscleActivation {
pub f_max: f64,
pub l_opt: f64,
pub v_max: f64,
pub alpha_opt: f64,
pub tau_act: f64,
pub tau_deact: f64,
pub(super) activation: f64,
}
impl HillMuscleActivation {
pub fn new(
f_max: f64,
l_opt: f64,
v_max: f64,
alpha_opt: f64,
tau_act: f64,
tau_deact: f64,
) -> Self {
Self {
f_max,
l_opt,
v_max,
alpha_opt,
tau_act,
tau_deact,
activation: 0.0,
}
}
pub fn skeletal_muscle(f_max: f64, l_opt: f64) -> Self {
Self::new(f_max, l_opt, 10.0 * l_opt, 0.0, 0.01, 0.04)
}
pub fn set_activation(&mut self, a: f64) {
self.activation = clamp(a, 0.0, 1.0);
}
pub fn activation(&self) -> f64 {
self.activation
}
pub fn update_activation(&mut self, u: f64, dt: f64) {
let u_clamped = clamp(u, 0.0, 1.0);
let tau = if u_clamped > self.activation {
self.tau_act
} else {
self.tau_deact
};
let da = (u_clamped - self.activation) / tau * dt;
self.activation = clamp(self.activation + da, 0.0, 1.0);
}
pub fn force_length(&self, fiber_length: f64) -> f64 {
let l_norm = fiber_length / self.l_opt;
let gamma = 0.45;
(-(((l_norm - 1.0) / gamma).powi(2))).exp()
}
pub fn force_velocity(&self, velocity: f64) -> f64 {
let v_norm = velocity / self.v_max;
let a_hill = 0.25;
if v_norm <= 0.0 {
let v_abs = (-v_norm).min(0.99);
(1.0 - v_abs) / (1.0 + v_abs / a_hill)
} else {
let f_ecc_max = 1.8;
let v_clamped = v_norm.min(0.99);
1.0 + (f_ecc_max - 1.0) * v_clamped / (v_clamped + 0.1)
}
}
pub fn passive_force_length(&self, fiber_length: f64) -> f64 {
let l_norm = fiber_length / self.l_opt;
if l_norm > 1.0 {
let strain = l_norm - 1.0;
0.05 * ((10.0 * strain).exp() - 1.0)
} else {
0.0
}
}
pub fn compute_force(&self, fiber_length: f64, velocity: f64) -> f64 {
let f_l = self.force_length(fiber_length);
let f_v = self.force_velocity(velocity);
let f_pe = self.passive_force_length(fiber_length);
let cos_alpha = self.alpha_opt.cos();
self.f_max * (self.activation * f_l * f_v + f_pe) * cos_alpha
}
pub fn active_stress(&self, fiber_length: f64, velocity: f64, area: f64) -> f64 {
if area > 1e-15 {
self.compute_force(fiber_length, velocity) / area
} else {
0.0
}
}
}
#[derive(Debug, Clone)]
pub struct FungHyperelastic {
pub c: f64,
pub d: [f64; 6],
}
impl FungHyperelastic {
pub fn new(c: f64, d: [f64; 6]) -> Self {
Self { c, d }
}
pub fn isotropic(c: f64, d_val: f64) -> Self {
Self::new(c, [d_val, d_val, d_val, 0.0, 0.0, 0.0])
}
pub fn compute_q(&self, e: &[[f64; 3]; 3]) -> f64 {
let e11 = e[0][0];
let e22 = e[1][1];
let e33 = e[2][2];
let e12 = e[0][1];
let e13 = e[0][2];
let e23 = e[1][2];
self.d[0] * e11 * e11
+ self.d[1] * e22 * e22
+ self.d[2] * e33 * e33
+ 2.0 * self.d[3] * e11 * e22
+ 2.0 * self.d[4] * e11 * e33
+ 2.0 * self.d[5] * e22 * e33
+ 4.0 * self.d[0].min(self.d[1]) * e12 * e12
+ 4.0 * self.d[0].min(self.d[2]) * e13 * e13
+ 4.0 * self.d[1].min(self.d[2]) * e23 * e23
}
pub fn strain_energy(&self, f: &[[f64; 3]; 3]) -> f64 {
let e = green_lagrange(f);
let q = self.compute_q(&e);
0.5 * self.c * (q.exp() - 1.0)
}
pub fn second_piola_kirchhoff(&self, f: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
let e = green_lagrange(f);
let q = self.compute_q(&e);
let exp_q = q.exp();
let e11 = e[0][0];
let e22 = e[1][1];
let e33 = e[2][2];
let e12 = e[0][1];
let e13 = e[0][2];
let e23 = e[1][2];
let dq_de11 = 2.0 * self.d[0] * e11 + 2.0 * self.d[3] * e22 + 2.0 * self.d[4] * e33;
let dq_de22 = 2.0 * self.d[1] * e22 + 2.0 * self.d[3] * e11 + 2.0 * self.d[5] * e33;
let dq_de33 = 2.0 * self.d[2] * e33 + 2.0 * self.d[4] * e11 + 2.0 * self.d[5] * e22;
let dq_de12 = 4.0 * self.d[0].min(self.d[1]) * e12;
let dq_de13 = 4.0 * self.d[0].min(self.d[2]) * e13;
let dq_de23 = 4.0 * self.d[1].min(self.d[2]) * e23;
let factor = self.c * exp_q;
[
[factor * dq_de11, factor * dq_de12, factor * dq_de13],
[factor * dq_de12, factor * dq_de22, factor * dq_de23],
[factor * dq_de13, factor * dq_de23, factor * dq_de33],
]
}
pub fn tangent_modulus(&self, f: &[[f64; 3]; 3]) -> f64 {
let e = green_lagrange(f);
let q = self.compute_q(&e);
self.c * q.exp() * (1.0 + q)
}
}